Fixed crash when pressing menu/settings button and fixed crash on first boot.

This commit is contained in:
izzy2lost
2025-10-21 03:55:04 -04:00
parent acceb734d8
commit c22353ebb2
3 changed files with 109 additions and 62 deletions
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId "com.izzy2lost.psx2"
minSdk 26
targetSdk 36
versionCode 9
versionName "1.0.7"
versionCode 10
versionName "1.0.8"
// APK
setProperty("archivesBaseName","PSX2_${versionCode}_${new Date().format('yyyyMMddHHmm')}")
@@ -189,17 +189,25 @@ public class GamesCoverDialogFragment extends DialogFragment {
coverUrls = new String[uris.length];
localPaths = new String[uris.length];
SharedPreferences prefs = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE);
boolean isFirstBoot = !prefs.getBoolean("has_resolved_titles_once", false);
for (int i = 0; i < uris.length; i++) {
String saved = prefs.getString("serial:" + uris[i], null);
String serial = saved;
// On first boot, skip native serial extraction to avoid crashes
// The background thread will handle it later with proper delays
if (serial == null || serial.isEmpty()) {
try {
String nativeSerial = NativeApp.getGameSerialSafe(uris[i]);
if (nativeSerial != null && !nativeSerial.isEmpty()) {
serial = normalizeSerial(nativeSerial);
prefs.edit().putString("serial:" + uris[i], serial).apply();
if (!isFirstBoot) {
try {
String nativeSerial = NativeApp.getGameSerialSafe(uris[i]);
if (nativeSerial != null && !nativeSerial.isEmpty()) {
serial = normalizeSerial(nativeSerial);
prefs.edit().putString("serial:" + uris[i], serial).apply();
}
} catch (Throwable e) {
android.util.Log.w("GamesCoverDialog", "Error getting serial for " + uris[i] + ": " + e.getMessage());
}
} catch (Throwable ignored) {}
}
}
if (serial == null || serial.isEmpty()) {
serial = buildSerialFromUri(uris[i]);
@@ -295,25 +303,66 @@ public class GamesCoverDialogFragment extends DialogFragment {
// Resolve proper game titles using local YAML index if available (GameIndex/Redump).
// Falls back to native URI API, then filename if needed.
// Capture context early to avoid requireContext() crashes if fragment detaches
final Context ctx = requireContext().getApplicationContext();
new Thread(() -> {
boolean changed = false;
for (int i = 0; i < uris.length; i++) {
try {
String t = TitleResolver.resolveTitleForUri(requireContext(), uris[i], titles[i]);
if (t != null && !t.isEmpty() && i < titles.length && !t.equals(titles[i])) {
titles[i] = t;
if (origTitles != null && i < origTitles.length) origTitles[i] = t;
changed = true;
}
} catch (Throwable ignored) {}
}
if (changed && isAdded()) requireActivity().runOnUiThread(() -> {
if (sortMode != SORT_ALPHA || (query != null && !query.isEmpty())) {
applyFilterAndSort();
} else {
adapter.notifyDataSetChanged();
try {
// On first boot, add a delay to let native library fully initialize
if (isFirstBoot) {
Thread.sleep(2000); // 2 second delay on first boot
}
});
// Check if fragment is still attached before proceeding
if (!isAdded()) return;
boolean changed = false;
for (int i = 0; i < uris.length; i++) {
// Check if fragment is still attached on each iteration
if (!isAdded()) break;
try {
String t = TitleResolver.resolveTitleForUri(ctx, uris[i], titles[i]);
if (t != null && !t.isEmpty() && i < titles.length && !t.equals(titles[i])) {
titles[i] = t;
if (origTitles != null && i < origTitles.length) origTitles[i] = t;
changed = true;
}
} catch (Throwable e) {
android.util.Log.w("GamesCoverDialog", "Error resolving title for " + uris[i] + ": " + e.getMessage());
}
}
// Mark that we've resolved titles at least once
if (isFirstBoot && isAdded()) {
try {
ctx.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
.edit().putBoolean("has_resolved_titles_once", true).apply();
} catch (Throwable ignored) {}
}
// Only update UI if fragment is still attached
if (changed && isAdded()) {
try {
requireActivity().runOnUiThread(() -> {
try {
if (!isAdded()) return;
if (sortMode != SORT_ALPHA || (query != null && !query.isEmpty())) {
applyFilterAndSort();
} else {
adapter.notifyDataSetChanged();
}
} catch (Throwable e) {
android.util.Log.w("GamesCoverDialog", "Error updating UI after title resolution: " + e.getMessage());
}
});
} catch (Throwable e) {
android.util.Log.w("GamesCoverDialog", "Error posting to UI thread: " + e.getMessage());
}
}
} catch (Throwable e) {
android.util.Log.e("GamesCoverDialog", "Error in title resolution thread: " + e.getMessage());
}
}).start();
// Dynamically size items based on RecyclerView size and orientation
@@ -491,26 +491,30 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
}
updateUiForControllerPresence();
// After initial BIOS auto-boot, gently open the Games dialog
// Only when setup is complete and this is app launch (not rotation)
if (getSharedPreferences("app_prefs", MODE_PRIVATE).getBoolean("first_run_done", false)) {
try {
final View decor = (getWindow() != null) ? getWindow().getDecorView() : null;
if (decor != null) {
decor.postDelayed(() -> {
if (!isFinishing() && !mSetupWizardActive) {
openGamesDialog();
}
}, 1600); // small delay to let BIOS boot briefly
}
} catch (Throwable ignored) {}
}
// Show first-run setup wizard if needed
if (!getSharedPreferences("app_prefs", MODE_PRIVATE).getBoolean("first_run_done", false)) {
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
boolean firstRunDone = prefs.getBoolean("first_run_done", false);
if (!firstRunDone) {
SetupWizardDialogFragment f = SetupWizardDialogFragment.newInstance();
f.setCancelable(false);
f.show(getSupportFragmentManager(), "setup_wizard");
} else {
// Only auto-open games dialog if this is NOT the first boot after setup
// (Setup wizard handles opening the games dialog on first completion)
boolean hasOpenedGamesAfterSetup = prefs.getBoolean("has_opened_games_after_setup", false);
if (hasOpenedGamesAfterSetup) {
try {
final View decor = (getWindow() != null) ? getWindow().getDecorView() : null;
if (decor != null) {
decor.postDelayed(() -> {
if (!isFinishing() && !mSetupWizardActive) {
openGamesDialog();
}
}, 1600); // small delay to let BIOS boot briefly
}
} catch (Throwable ignored) {}
}
}
// Setup right drawer quick actions
@@ -541,6 +545,12 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
// Public method to open the games covers dialog via controller quick actions
public void openGamesDialog() {
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
// Mark that we've opened games dialog after setup (prevents double-open on first boot)
if (!prefs.getBoolean("has_opened_games_after_setup", false)) {
prefs.edit().putBoolean("has_opened_games_after_setup", true).apply();
}
String folderUri = prefs.getString("games_folder_uri", null);
if (TextUtils.isEmpty(folderUri)) {
pickGamesFolder();
@@ -568,33 +578,13 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
if (btn_settings != null) {
btn_settings.setOnClickListener(v -> {
try {
// Pause game when opening settings drawer using the same logic as the pause/play button
if (hasSelectedGame() && isThread() && !NativeApp.isPaused()) {
togglePauseState(); // This will pause the game and update button state
}
// Get drawer layout first
// Just open the drawer - let the drawer listener handle pausing
DrawerLayout drawer = findViewById(R.id.drawer_layout);
if (drawer != null) {
// Refresh drawer settings before opening - add error handling
try {
refreshDrawerSettings();
} catch (Exception e) {
android.util.Log.e("MainActivity", "Error refreshing drawer settings: " + e.getMessage());
// Continue anyway even if refresh fails
}
drawer.openDrawer(androidx.core.view.GravityCompat.START);
}
} catch (Throwable t) {
android.util.Log.e("MainActivity", "Error opening settings drawer: " + t.getMessage());
// Try to open drawer directly as fallback
try {
DrawerLayout drawer = findViewById(R.id.drawer_layout);
if (drawer != null) {
drawer.openDrawer(androidx.core.view.GravityCompat.START);
}
} catch (Throwable fallback) {
android.util.Log.e("MainActivity", "Fallback drawer open also failed: " + fallback.getMessage());
}
}
});
}
@@ -2449,6 +2439,14 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
public void onDrawerOpened() {
mDrawerOpen = true;
android.util.Log.d("DrawerTracking", "Drawer opened");
// Refresh drawer settings to reflect current state
try {
refreshDrawerSettings();
} catch (Exception e) {
android.util.Log.e("DrawerTracking", "Error refreshing drawer settings: " + e.getMessage());
}
// Drawer opened, pause the game
try {
if (hasSelectedGame() && isThread() && !NativeApp.isPaused()) {