fix some crashes better stability

This commit is contained in:
izzy2lost
2025-10-16 20:39:57 -04:00
parent 9a38c38ef0
commit b9bad17974
9 changed files with 395 additions and 138 deletions
@@ -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() {
@@ -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() {
@@ -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,
@@ -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);
@@ -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
@@ -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();
@@ -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) {}
}
}
@@ -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;
@@ -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);
}
}
}