diff --git a/app/src/main/java/com/izzy2lost/psx2/CheatsDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/CheatsDialogFragment.java index 798d623..24a861e 100644 --- a/app/src/main/java/com/izzy2lost/psx2/CheatsDialogFragment.java +++ b/app/src/main/java/com/izzy2lost/psx2/CheatsDialogFragment.java @@ -30,6 +30,13 @@ public class CheatsDialogFragment extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + // Notify MainActivity that this dialog is opening + try { + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).onDialogOpened(); + } + } catch (Throwable ignored) {} + Context ctx = requireContext(); View view = LayoutInflater.from(ctx).inflate(R.layout.simple_list, null, false); ListView lv = view.findViewById(android.R.id.list); @@ -43,12 +50,23 @@ public class CheatsDialogFragment extends DialogFragment { refreshList(); - return new MaterialAlertDialogBuilder(ctx, com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog) + Dialog dialog = new MaterialAlertDialogBuilder(ctx, com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog) .setCustomTitle(UiUtils.centeredDialogTitle(ctx, "Manage Cheats")) .setView(view) .setNegativeButton("Close", null) .setPositiveButton("Import For Game", (d, w) -> startImport()) .create(); + + // Resume game when dialog is dismissed + dialog.setOnDismissListener(d -> { + android.util.Log.d("CheatsDialog", "Cheats dialog dismissed"); + // Use the global dialog tracking system + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).onDialogClosed(); + } + }); + + return dialog; } private void refreshList() { diff --git a/app/src/main/java/com/izzy2lost/psx2/ControllerTestDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/ControllerTestDialogFragment.java index 6c8574e..2f715c5 100644 --- a/app/src/main/java/com/izzy2lost/psx2/ControllerTestDialogFragment.java +++ b/app/src/main/java/com/izzy2lost/psx2/ControllerTestDialogFragment.java @@ -30,6 +30,13 @@ public class ControllerTestDialogFragment extends DialogFragment implements Cont @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + // Notify MainActivity that this dialog is opening + try { + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).onDialogOpened(); + } + } catch (Throwable ignored) {} + View view = getLayoutInflater().inflate(R.layout.dialog_controller_test, null); mControllerListText = view.findViewById(R.id.tv_controller_list); @@ -41,11 +48,22 @@ public class ControllerTestDialogFragment extends DialogFragment implements Cont // Update controller list updateControllerList(); - return new MaterialAlertDialogBuilder(requireContext()) + Dialog dialog = new MaterialAlertDialogBuilder(requireContext()) .setTitle("Controller Test") .setView(view) .setPositiveButton("Close", null) .create(); + + // Resume game when dialog is dismissed + dialog.setOnDismissListener(d -> { + android.util.Log.d("ControllerTestDialog", "Controller test dialog dismissed"); + // Use the global dialog tracking system + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).onDialogClosed(); + } + }); + + return dialog; } private void updateControllerList() { diff --git a/app/src/main/java/com/izzy2lost/psx2/GameSettingsDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/GameSettingsDialogFragment.java index 584e328..7ce0122 100644 --- a/app/src/main/java/com/izzy2lost/psx2/GameSettingsDialogFragment.java +++ b/app/src/main/java/com/izzy2lost/psx2/GameSettingsDialogFragment.java @@ -41,17 +41,26 @@ public class GameSettingsDialogFragment extends DialogFragment { fragment.setArguments(args); return fragment; } + + @Override + public void onDestroy() { + super.onDestroy(); + // Dialog is being destroyed - resume the game + android.util.Log.d("GameSettingsDialog", "onDestroy called - resuming game"); + try { + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).onDialogClosed(); + } + } catch (Throwable ignored) {} + } @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - // Pause game when game settings dialog is shown + // Notify MainActivity that this dialog is opening try { if (getActivity() instanceof MainActivity) { - MainActivity mainActivity = (MainActivity) getActivity(); - if (mainActivity.hasSelectedGame() && mainActivity.isEmulationThreadRunning() && !NativeApp.isPaused()) { - NativeApp.pause(); - } + ((MainActivity) getActivity()).onDialogOpened(); } } catch (Throwable ignored) {} @@ -381,21 +390,7 @@ public class GameSettingsDialogFragment extends DialogFragment { }); } - Dialog dialog = builder.create(); - - // Resume game when dialog is dismissed - dialog.setOnDismissListener(d -> { - try { - if (getActivity() instanceof MainActivity) { - MainActivity mainActivity = (MainActivity) getActivity(); - if (mainActivity.hasSelectedGame() && mainActivity.isEmulationThreadRunning() && NativeApp.isPaused()) { - NativeApp.resume(); - } - } - } catch (Throwable ignored) {} - }); - - return dialog; + return builder.create(); } private void saveGameSettings(String gameSerial, String gameCrc, diff --git a/app/src/main/java/com/izzy2lost/psx2/GamesCoverDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/GamesCoverDialogFragment.java index b5ad6d8..4d5a2c2 100644 --- a/app/src/main/java/com/izzy2lost/psx2/GamesCoverDialogFragment.java +++ b/app/src/main/java/com/izzy2lost/psx2/GamesCoverDialogFragment.java @@ -98,6 +98,18 @@ public class GamesCoverDialogFragment extends DialogFragment { forceDialogImmersive(); if (rv != null) applyCoverflowTransforms(rv); } + + @Override + public void onDestroy() { + super.onDestroy(); + // Dialog is being destroyed - resume the game + android.util.Log.d("GamesCoverDialog", "onDestroy called - resuming game"); + try { + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).onDialogClosed(); + } + } catch (Throwable ignored) {} + } @Override public void onConfigurationChanged(@NonNull android.content.res.Configuration newConfig) { @@ -117,13 +129,10 @@ public class GamesCoverDialogFragment extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - // Pause game when games cover dialog is shown + // Notify MainActivity that this dialog is opening try { if (getActivity() instanceof MainActivity) { - MainActivity mainActivity = (MainActivity) getActivity(); - if (mainActivity.hasSelectedGame() && mainActivity.isEmulationThreadRunning() && !NativeApp.isPaused()) { - NativeApp.pause(); - } + ((MainActivity) getActivity()).onDialogOpened(); } } catch (Throwable ignored) {} @@ -132,18 +141,6 @@ public class GamesCoverDialogFragment extends DialogFragment { // Ensure immersive as soon as window exists try { applyImmersiveToWindow(d.getWindow()); } catch (Throwable ignored) {} - // Resume game when dialog is dismissed - d.setOnDismissListener(dialog -> { - try { - if (getActivity() instanceof MainActivity) { - MainActivity mainActivity = (MainActivity) getActivity(); - if (mainActivity.hasSelectedGame() && mainActivity.isEmulationThreadRunning() && NativeApp.isPaused()) { - NativeApp.resume(); - } - } - } catch (Throwable ignored) {} - }); - return d; } @@ -375,6 +372,36 @@ public class GamesCoverDialogFragment extends DialogFragment { } catch (Throwable ignored) {} }); + // Setup drawer listener for pause/resume tracking + try { + androidx.drawerlayout.widget.DrawerLayout dialogDrawer = root.findViewById(R.id.dlg_drawer_layout); + if (dialogDrawer != null) { + dialogDrawer.addDrawerListener(new androidx.drawerlayout.widget.DrawerLayout.DrawerListener() { + @Override + public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {} + + @Override + public void onDrawerOpened(@NonNull View drawerView) { + // Notify MainActivity that drawer opened (will pause game) + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).onDrawerOpened(); + } + } + + @Override + public void onDrawerClosed(@NonNull View drawerView) { + // Notify MainActivity that drawer closed (will resume if no other dialogs open) + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).onDrawerClosed(); + } + } + + @Override + public void onDrawerStateChanged(int newState) {} + }); + } + } catch (Throwable ignored) {} + // Wire in-dialog navigation header actions to mirror main drawer try { com.google.android.material.navigation.NavigationView nav = root.findViewById(R.id.dialog_nav_view); diff --git a/app/src/main/java/com/izzy2lost/psx2/MainActivity.java b/app/src/main/java/com/izzy2lost/psx2/MainActivity.java index d1e9e50..96aba19 100644 --- a/app/src/main/java/com/izzy2lost/psx2/MainActivity.java +++ b/app/src/main/java/com/izzy2lost/psx2/MainActivity.java @@ -88,8 +88,12 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF private AlertDialog mBiosPromptDialog = null; private boolean mControllerHintShowing = false; private boolean mPreviousControllerState = false; + + // Global dialog tracking for pause/resume + private int mOpenDialogCount = 0; + private boolean mDrawerOpen = false; - private boolean isThread() { + public boolean isThread() { if (mEmulationThread != null) { Thread.State _thread_state = mEmulationThread.getState(); return _thread_state == Thread.State.BLOCKED @@ -553,9 +557,9 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF if (btn_settings != null) { btn_settings.setOnClickListener(v -> { try { - // Pause game when opening settings drawer + // Pause game when opening settings drawer using the same logic as the pause/play button if (hasSelectedGame() && isThread() && !NativeApp.isPaused()) { - NativeApp.pause(); + togglePauseState(); // This will pause the game and update button state } // Get drawer layout first DrawerLayout drawer = findViewById(R.id.drawer_layout); @@ -1336,19 +1340,59 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF private void importSingleBiosUri(Uri uri, File biosDir) { if (uri == null) return; - String displayName = getDisplayNameFromUri(this, uri); - if (TextUtils.isEmpty(displayName)) displayName = "bios.bin"; - File outFile = new File(biosDir, displayName); - copyUriToFile(this, uri, outFile); - // Also mirror to SAF data root if set - android.net.Uri dataRoot = SafManager.getDataRootUri(this); - if (dataRoot != null) { - androidx.documentfile.provider.DocumentFile target = SafManager.createChild(this, new String[]{"bios"}, displayName, "application/octet-stream"); - if (target != null) { - try (java.io.InputStream in = getContentResolver().openInputStream(uri)) { - SafManager.copyFromStream(this, in, target.getUri()); - } catch (Exception ignored) {} + + try { + String displayName = getDisplayNameFromUri(this, uri); + if (TextUtils.isEmpty(displayName)) displayName = "bios.bin"; + File outFile = new File(biosDir, displayName); + + // Check if file already exists and is valid + if (outFile.exists() && outFile.length() > 0) { + android.util.Log.d("MainActivity", "BIOS file already exists: " + displayName); + return; } + + // Ensure bios directory exists + if (!biosDir.exists()) { + if (!biosDir.mkdirs()) { + android.util.Log.e("MainActivity", "Failed to create BIOS directory: " + biosDir.getAbsolutePath()); + return; + } + } + + // Copy the file with improved error handling + boolean success = copyUriToFile(this, uri, outFile); + if (!success) { + android.util.Log.e("MainActivity", "Failed to copy BIOS file: " + displayName); + return; + } + + // Also mirror to SAF data root if set (with error handling) + android.net.Uri dataRoot = SafManager.getDataRootUri(this); + if (dataRoot != null) { + try { + androidx.documentfile.provider.DocumentFile target = SafManager.createChild(this, new String[]{"bios"}, displayName, "application/octet-stream"); + if (target != null) { + try (java.io.InputStream in = getContentResolver().openInputStream(uri)) { + if (in != null) { + SafManager.copyFromStream(this, in, target.getUri()); + android.util.Log.d("MainActivity", "BIOS file mirrored to SAF: " + displayName); + } + } catch (Exception e) { + android.util.Log.w("MainActivity", "Failed to mirror BIOS to SAF: " + e.getMessage()); + // Don't fail the import if SAF mirroring fails + } + } + } catch (Exception e) { + android.util.Log.w("MainActivity", "Failed to create SAF target for BIOS: " + e.getMessage()); + // Don't fail the import if SAF mirroring fails + } + } + + android.util.Log.d("MainActivity", "BIOS import completed successfully: " + displayName); + + } catch (Exception e) { + android.util.Log.e("MainActivity", "Error during BIOS import: " + e.getMessage()); } } @@ -1397,14 +1441,49 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF is = context.getContentResolver().openInputStream(uri); if (is == null) return false; os = new FileOutputStream(destFile); - byte[] buffer = new byte[8192]; + + // Use smaller buffer size for lower-end devices to reduce memory pressure + byte[] buffer = new byte[4096]; // Reduced from 8192 int read; + long totalBytes = 0; + long maxFileSize = 16 * 1024 * 1024; // 16MB max BIOS file size limit + while ((read = is.read(buffer)) != -1) { + totalBytes += read; + + // Check file size limit to prevent memory issues on lower-end devices + if (totalBytes > maxFileSize) { + android.util.Log.w("MainActivity", "BIOS file too large: " + totalBytes + " bytes"); + return false; + } + os.write(buffer, 0, read); + + // Force periodic flush to reduce memory pressure + if (totalBytes % (64 * 1024) == 0) { // Flush every 64KB + os.flush(); + } } os.flush(); + + // Verify the file was copied successfully + if (destFile.length() == 0) { + android.util.Log.e("MainActivity", "BIOS file copy failed: empty file"); + return false; + } + + android.util.Log.d("MainActivity", "BIOS file copied successfully: " + destFile.getName() + + " (" + destFile.length() + " bytes)"); return true; + } catch (OutOfMemoryError e) { + android.util.Log.e("MainActivity", "Out of memory during BIOS file copy: " + e.getMessage()); + // Try to clean up partial file + try { if (destFile.exists()) destFile.delete(); } catch (Exception ignored) {} + return false; } catch (Exception e) { + android.util.Log.e("MainActivity", "Error copying BIOS file: " + e.getMessage()); + // Try to clean up partial file + try { if (destFile.exists()) destFile.delete(); } catch (Exception ignored) {} return false; } finally { try { if (is != null) is.close(); } catch (Exception ignored) {} @@ -1802,8 +1881,15 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF android.util.Log.d("Controller", "Controller " + controllerId + " combo: " + comboName); if ("select_start".equals(comboName)) { - // Show quick actions dialog + // Show quick actions dialog with proper dialog tracking runOnUiThread(() -> { + // Pause the game immediately when controller combo is detected using the same logic as the pause/play button + if (hasSelectedGame() && isThread() && !NativeApp.isPaused()) { + try { + togglePauseState(); // This will pause the game and update button state + } catch (Throwable ignored) {} + } + QuickActionsDialogFragment dialog = new QuickActionsDialogFragment(); dialog.show(getSupportFragmentManager(), "quick_actions"); }); @@ -2215,16 +2301,27 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF public void togglePauseState() { try { boolean isPaused = NativeApp.isPaused(); + boolean hasGame = hasSelectedGame() && isThread(); + android.util.Log.d("TogglePause", "togglePauseState called. isPaused: " + isPaused + ", hasGame: " + hasGame); + if (isPaused) { + android.util.Log.d("TogglePause", "Resuming game"); NativeApp.resume(); } else { + android.util.Log.d("TogglePause", "Pausing game"); NativeApp.pause(); } updatePausePlayButton(); - } catch (Throwable ignored) {} + + // Log the state after the change + boolean newIsPaused = NativeApp.isPaused(); + android.util.Log.d("TogglePause", "After toggle. New isPaused: " + newIsPaused); + } catch (Throwable e) { + android.util.Log.e("TogglePause", "Error in togglePauseState: " + e.getMessage()); + } } - private void updatePausePlayButton() { + public void updatePausePlayButton() { MaterialButton btn_pause_play = findViewById(R.id.btn_pause_play); if (btn_pause_play != null) { try { @@ -2297,6 +2394,65 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF .show(); } + // Global dialog tracking methods + public void onDialogOpened() { + mOpenDialogCount++; + android.util.Log.d("DialogTracking", "Dialog opened. Count: " + mOpenDialogCount); + if (mOpenDialogCount == 1 && !mDrawerOpen) { + // First dialog opened and no drawer is open, pause the game + try { + if (hasSelectedGame() && isThread() && !NativeApp.isPaused()) { + android.util.Log.d("DialogTracking", "Pausing game on dialog open"); + NativeApp.pause(); + updatePausePlayButton(); + } + } catch (Throwable ignored) {} + } + } + + public void onDialogClosed() { + mOpenDialogCount = Math.max(0, mOpenDialogCount - 1); + android.util.Log.d("DialogTracking", "Dialog closed. Count: " + mOpenDialogCount + ", Drawer open: " + mDrawerOpen); + if (mOpenDialogCount == 0 && !mDrawerOpen) { + // All dialogs closed and no drawers open, resume the game + try { + if (hasSelectedGame() && isThread() && NativeApp.isPaused()) { + android.util.Log.d("DialogTracking", "Resuming game on dialog close"); + NativeApp.resume(); + updatePausePlayButton(); + } + } catch (Throwable ignored) {} + } + } + + public void onDrawerOpened() { + mDrawerOpen = true; + android.util.Log.d("DrawerTracking", "Drawer opened"); + // Drawer opened, pause the game + try { + if (hasSelectedGame() && isThread() && !NativeApp.isPaused()) { + android.util.Log.d("DrawerTracking", "Pausing game on drawer open"); + NativeApp.pause(); + updatePausePlayButton(); + } + } catch (Throwable ignored) {} + } + + public void onDrawerClosed() { + mDrawerOpen = false; + android.util.Log.d("DrawerTracking", "Drawer closed. Dialog count: " + mOpenDialogCount); + if (mOpenDialogCount == 0 && !mDrawerOpen) { + // All dialogs closed and no drawers open, resume the game + try { + if (hasSelectedGame() && isThread() && NativeApp.isPaused()) { + android.util.Log.d("DrawerTracking", "Resuming game on drawer close"); + NativeApp.resume(); + updatePausePlayButton(); + } + } catch (Throwable ignored) {} + } + } + private void setupDrawerListeners() { try { DrawerLayout drawer = findViewById(R.id.drawer_layout); @@ -2307,22 +2463,12 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF @Override public void onDrawerOpened(@NonNull View drawerView) { - // Pause game when any drawer is opened - try { - if (hasSelectedGame() && isThread() && !NativeApp.isPaused()) { - NativeApp.pause(); - } - } catch (Throwable ignored) {} + MainActivity.this.onDrawerOpened(); } @Override public void onDrawerClosed(@NonNull View drawerView) { - // Resume game when all drawers are closed - try { - if (hasSelectedGame() && isThread() && NativeApp.isPaused()) { - NativeApp.resume(); - } - } catch (Throwable ignored) {} + MainActivity.this.onDrawerClosed(); } @Override diff --git a/app/src/main/java/com/izzy2lost/psx2/QuickActionsDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/QuickActionsDialogFragment.java index 4a328c8..b447f95 100644 --- a/app/src/main/java/com/izzy2lost/psx2/QuickActionsDialogFragment.java +++ b/app/src/main/java/com/izzy2lost/psx2/QuickActionsDialogFragment.java @@ -21,13 +21,10 @@ public class QuickActionsDialogFragment extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - // Pause game when quick actions dialog is shown + // Notify MainActivity that this dialog is opening try { if (getActivity() instanceof MainActivity) { - MainActivity mainActivity = (MainActivity) getActivity(); - if (mainActivity.hasSelectedGame() && mainActivity.isEmulationThreadRunning() && !NativeApp.isPaused()) { - NativeApp.pause(); - } + ((MainActivity) getActivity()).onDialogOpened(); } } catch (Throwable ignored) {} @@ -228,7 +225,8 @@ public class QuickActionsDialogFragment extends DialogFragment { if (btnGames != null) { btnGames.setOnClickListener(v -> { if (requireActivity() instanceof MainActivity) { - ((MainActivity) requireActivity()).openGamesDialog(); + MainActivity mainActivity = (MainActivity) requireActivity(); + mainActivity.openGamesDialog(); } dismissAllowingStateLoss(); }); @@ -259,7 +257,8 @@ public class QuickActionsDialogFragment extends DialogFragment { root.postDelayed(() -> { try { if (!activity.isFinishing()) { - ((MainActivity) activity).openGamesDialog(); + MainActivity mainActivity = (MainActivity) activity; + mainActivity.openGamesDialog(); } } catch (Throwable ignored) {} }, 300); @@ -272,26 +271,24 @@ public class QuickActionsDialogFragment extends DialogFragment { // Cancel button if (btnCancel != null) btnCancel.setOnClickListener(v -> dismissAllowingStateLoss()); - Dialog dialog = new MaterialAlertDialogBuilder(requireContext(), + return new MaterialAlertDialogBuilder(requireContext(), com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog) .setView(view) .create(); - - // Resume game when dialog is dismissed - dialog.setOnDismissListener(d -> { - try { - if (getActivity() instanceof MainActivity) { - MainActivity mainActivity = (MainActivity) getActivity(); - if (mainActivity.hasSelectedGame() && mainActivity.isEmulationThreadRunning() && NativeApp.isPaused()) { - NativeApp.resume(); - } - } - } catch (Throwable ignored) {} - }); - - return dialog; } + @Override + public void onDestroy() { + super.onDestroy(); + // Dialog is being destroyed - resume the game + android.util.Log.d("QuickActionsDialog", "onDestroy called - resuming game"); + try { + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).onDialogClosed(); + } + } catch (Throwable ignored) {} + } + private void quitApp() { // Stop emulator first NativeApp.shutdown(); diff --git a/app/src/main/java/com/izzy2lost/psx2/SavesDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/SavesDialogFragment.java index 3950bd9..a9620bf 100644 --- a/app/src/main/java/com/izzy2lost/psx2/SavesDialogFragment.java +++ b/app/src/main/java/com/izzy2lost/psx2/SavesDialogFragment.java @@ -148,13 +148,10 @@ public class SavesDialogFragment extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - // Pause game when saves dialog is shown + // Notify MainActivity that this dialog is opening try { if (getActivity() instanceof MainActivity) { - MainActivity mainActivity = (MainActivity) getActivity(); - if (mainActivity.hasSelectedGame() && mainActivity.isEmulationThreadRunning() && !NativeApp.isPaused()) { - NativeApp.pause(); - } + ((MainActivity) getActivity()).onDialogOpened(); } } catch (Throwable ignored) {} @@ -192,7 +189,6 @@ public class SavesDialogFragment extends DialogFragment { // Success - refresh the dialog or close it dismiss(); } - NativeApp.resume(); } @Override @@ -201,7 +197,6 @@ public class SavesDialogFragment extends DialogFragment { // Success dismiss(); } - NativeApp.resume(); } }); @@ -213,20 +208,18 @@ public class SavesDialogFragment extends DialogFragment { .setView(view) .setNegativeButton("Cancel", (d, w) -> d.dismiss()); - Dialog dialog = builder.create(); - - // Resume game when dialog is dismissed - dialog.setOnDismissListener(d -> { - try { - if (getActivity() instanceof MainActivity) { - MainActivity mainActivity = (MainActivity) getActivity(); - if (mainActivity.hasSelectedGame() && mainActivity.isEmulationThreadRunning() && NativeApp.isPaused()) { - NativeApp.resume(); - } - } - } catch (Throwable ignored) {} - }); - - return dialog; + return builder.create(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + // Dialog is being destroyed - resume the game + android.util.Log.d("SavesDialog", "onDestroy called - resuming game"); + try { + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).onDialogClosed(); + } + } catch (Throwable ignored) {} } } diff --git a/app/src/main/java/com/izzy2lost/psx2/SettingsDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/SettingsDialogFragment.java index 8258951..23eb815 100644 --- a/app/src/main/java/com/izzy2lost/psx2/SettingsDialogFragment.java +++ b/app/src/main/java/com/izzy2lost/psx2/SettingsDialogFragment.java @@ -72,15 +72,10 @@ public class SettingsDialogFragment extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - // Pause game when settings dialog is shown - try { - if (getActivity() instanceof MainActivity) { - MainActivity mainActivity = (MainActivity) getActivity(); - if (mainActivity.hasSelectedGame() && mainActivity.isEmulationThreadRunning() && !NativeApp.isPaused()) { - NativeApp.pause(); - } - } - } catch (Throwable ignored) {} + // Notify main activity that dialog is opening + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).onDialogOpened(); + } Context ctx = requireContext(); View view = getLayoutInflater().inflate(R.layout.dialog_settings, null, false); @@ -294,14 +289,11 @@ public class SettingsDialogFragment extends DialogFragment { // Resume game when dialog is dismissed dialog.setOnDismissListener(d -> { - try { - if (getActivity() instanceof MainActivity) { - MainActivity mainActivity = (MainActivity) getActivity(); - if (mainActivity.hasSelectedGame() && mainActivity.isEmulationThreadRunning() && NativeApp.isPaused()) { - NativeApp.resume(); - } - } - } catch (Throwable ignored) {} + android.util.Log.d("SettingsDialog", "Settings dialog dismissed"); + // Use the global dialog tracking system + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).onDialogClosed(); + } }); return dialog; diff --git a/app/src/main/java/com/izzy2lost/psx2/SetupWizardDialogFragment.java b/app/src/main/java/com/izzy2lost/psx2/SetupWizardDialogFragment.java index 80388d8..5019c91 100644 --- a/app/src/main/java/com/izzy2lost/psx2/SetupWizardDialogFragment.java +++ b/app/src/main/java/com/izzy2lost/psx2/SetupWizardDialogFragment.java @@ -1,6 +1,7 @@ package com.izzy2lost.psx2; import android.app.Dialog; +import android.os.Build; import android.os.Bundle; import android.view.View; import android.view.Gravity; @@ -28,12 +29,66 @@ public class SetupWizardDialogFragment extends DialogFragment { private TextView hintView; private Runnable mPeriodicCheck; private boolean mHasAutoAdvanced = false; + + // Method to detect if this is a lower-end device + private boolean isLowerEndDevice() { + try { + // Check available memory + Runtime runtime = Runtime.getRuntime(); + long maxMemory = runtime.maxMemory(); + long totalMemory = runtime.totalMemory(); + long freeMemory = runtime.freeMemory(); + long availableMemory = maxMemory - totalMemory + freeMemory; + + // Consider device lower-end if it has less than 512MB available memory + boolean lowMemory = availableMemory < 512 * 1024 * 1024; // 512MB + + // Check Android version - older versions might be on older hardware + boolean oldAndroid = Build.VERSION.SDK_INT < Build.VERSION_CODES.P; // Android 9+ + + // Check number of CPU cores + int cpuCores = runtime.availableProcessors(); + boolean fewCores = cpuCores <= 2; // 2 or fewer cores + + android.util.Log.d("SetupWizard", "Device specs - Available Memory: " + + (availableMemory / 1024 / 1024) + "MB, " + + "CPU Cores: " + cpuCores + + ", Android API: " + Build.VERSION.SDK_INT); + + return lowMemory || (oldAndroid && fewCores); + } catch (Exception e) { + android.util.Log.w("SetupWizard", "Error detecting device capabilities: " + e.getMessage()); + return false; // Assume not lower-end if we can't detect + } + } + + // Method to get appropriate timeout based on device capabilities + private long getTimeoutForDevice(long baseTimeout, long lowerEndTimeout) { + return isLowerEndDevice() ? lowerEndTimeout : baseTimeout; + } @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + // Notify MainActivity that this dialog is opening + try { + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).onDialogOpened(); + } + } catch (Throwable ignored) {} + Dialog d = new Dialog(requireContext(), R.style.PSX2_FullScreenDialog); d.setContentView(buildContent()); + + // Resume game when dialog is dismissed + d.setOnDismissListener(dialog -> { + android.util.Log.d("SetupWizardDialog", "Setup wizard dialog dismissed"); + // Use the global dialog tracking system + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).onDialogClosed(); + } + }); + return d; } @@ -141,13 +196,20 @@ public class SetupWizardDialogFragment extends DialogFragment { try { a = (MainActivity) requireActivity(); a.setSetupWizardActive(false); } catch (Throwable ignored) {} dismissAllowingStateLoss(); if (a != null) { - // Open the games dialog just like the old GAMES button + // Use adaptive delay based on device capabilities + long manualDelay = getTimeoutForDevice(2000, 4000); // 2s normal, 4s lower-end + android.util.Log.d("SetupWizard", "Using manual delay: " + manualDelay + "ms"); + final MainActivity act = a; View decor = act.getWindow() != null ? act.getWindow().getDecorView() : null; if (decor != null) { - decor.postDelayed(act::openGamesDialog, 150); + decor.postDelayed(() -> { + act.openGamesDialog(); + }, manualDelay); } else { - act.runOnUiThread(act::openGamesDialog); + act.runOnUiThread(() -> { + act.openGamesDialog(); + }); } } } @@ -310,7 +372,14 @@ public class SetupWizardDialogFragment extends DialogFragment { mHasAutoAdvanced = true; stopPeriodicCheck(); - // All steps complete, auto-advance with delay to let BIOS processing finish + // Use adaptive timeouts based on device capabilities + long autoAdvanceDelay = getTimeoutForDevice(2000, 4000); // 2s normal, 4s lower-end + long gamesDialogDelay = getTimeoutForDevice(3000, 6000); // 3s normal, 6s lower-end + + android.util.Log.d("SetupWizard", "Using timeouts - Auto-advance: " + autoAdvanceDelay + + "ms, Games dialog: " + gamesDialogDelay + "ms"); + + // All steps complete, auto-advance with adaptive delay View decor = getDialog() != null && getDialog().getWindow() != null ? getDialog().getWindow().getDecorView() : null; if (decor != null) { decor.postDelayed(() -> { @@ -320,13 +389,15 @@ public class SetupWizardDialogFragment extends DialogFragment { MainActivity a = (MainActivity) requireActivity(); a.setSetupWizardActive(false); dismissAllowingStateLoss(); - // Add extra delay before opening games dialog to avoid overload + // Add adaptive delay before opening games dialog View mainDecor = a.getWindow() != null ? a.getWindow().getDecorView() : null; if (mainDecor != null) { - mainDecor.postDelayed(a::openGamesDialog, 800); + mainDecor.postDelayed(() -> { + a.openGamesDialog(); + }, gamesDialogDelay); } } catch (Throwable ignored) {} - }, 500); + }, autoAdvanceDelay); } } }