diff --git a/core/oslib/oslib.cpp b/core/oslib/oslib.cpp index 976620a30..fd61df6f8 100644 --- a/core/oslib/oslib.cpp +++ b/core/oslib/oslib.cpp @@ -128,10 +128,22 @@ std::string getSavestatePath(int index, bool writable) state_file = state_file + index_str + ".state"; if (index == -1) state_file += ".net"; - if (writable) + + static std::string lastFile; + static std::string lastPath; + + if (writable) { + lastFile.clear(); return get_writable_data_path(state_file); + } else - return get_readonly_data_path(state_file); + { + if (lastFile != state_file) { + lastFile = state_file; + lastPath = get_readonly_data_path(state_file); + } + return lastPath; + } } std::string getShaderCachePath(const std::string& filename) diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/AndroidStorage.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/AndroidStorage.java index 0858f9928..df1fc3899 100644 --- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/AndroidStorage.java +++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/AndroidStorage.java @@ -71,11 +71,15 @@ public class AndroidStorage { else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) activity.getContentResolver().takePersistableUriPermission(uri, storageIntentPerms); + /* Use the uri path now to avoid issues when targeting sdk 30+ in the future String realPath = getRealPath(uri); - if (realPath != null) + // when targeting sdk 30+ (android 11+) using the real path doesn't work (empty content) -> *must* use the uri + int targetSdkVersion = activity.getApplication().getApplicationInfo().targetSdkVersion; + if (realPath != null && targetSdkVersion <= Build.VERSION_CODES.Q) addStorageCallback(realPath); else - addStorageCallback(uri.toString()); + */ + addStorageCallback(uri.toString()); } } @@ -160,7 +164,7 @@ public class AndroidStorage { intent = Intent.createChooser(intent, "Select a cheat file"); } else { - intent = Intent.createChooser(intent, "Select a directory"); + intent = Intent.createChooser(intent, "Select a content directory"); } storageIntentPerms = Intent.FLAG_GRANT_READ_URI_PERMISSION | (writeAccess ? Intent.FLAG_GRANT_WRITE_URI_PERMISSION : 0); intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | storageIntentPerms); diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/BaseGLActivity.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/BaseGLActivity.java index 9c9e91619..1d02e1dff 100644 --- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/BaseGLActivity.java +++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/BaseGLActivity.java @@ -3,6 +3,7 @@ package com.flycast.emulator; import android.Manifest; import android.app.Activity; import android.app.AlertDialog; +import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; @@ -26,7 +27,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; -import com.flycast.emulator.AndroidStorage; import com.flycast.emulator.config.Config; import com.flycast.emulator.emu.AudioBackend; import com.flycast.emulator.emu.HttpClient; @@ -35,6 +35,9 @@ import com.flycast.emulator.periph.InputDeviceManager; import com.flycast.emulator.periph.SipEmulator; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -91,8 +94,21 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat. OuyaController.init(this); new HttpClient().nativeInit(); - String home_directory = prefs.getString(Config.pref_home, ""); - String result = JNIdc.initEnvironment((Emulator)getApplicationContext(), getFilesDir().getAbsolutePath(), home_directory, + String homeDir = prefs.getString(Config.pref_home, ""); + // Check that home dir is valid, migrate if needed + String newHome = checkHomeDirectory(homeDir); + if (newHome != null) { + if (!newHome.equals(homeDir)) + prefs.edit().putString(Config.pref_home, newHome).apply(); + finishCreation(); + } + Log.i("flycast", "BaseGLActivity.onCreate done"); + } + + protected void finishCreation() + { + String homeDir = prefs.getString(Config.pref_home, getDefaultHomeDir()); + String result = JNIdc.initEnvironment((Emulator)getApplicationContext(), getFilesDir().getAbsolutePath(), homeDir, Locale.getDefault().toString()); if (result != null) { AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); @@ -158,7 +174,7 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat. pendingIntentUrl = gameUri.toString(); } } - Log.i("flycast", "BaseGLActivity.onCreate done"); + Log.i("flycast", "BaseGLActivity.finishCreation done"); } private void setStorageDirectories() @@ -395,6 +411,132 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat. } + private String getDefaultHomeDir() + { + return getExternalFilesDir(null).getAbsolutePath(); + } + + private String checkHomeDirectory(String homeDir) + { + if (homeDir.isEmpty()) + // home dir not set: use default + return getDefaultHomeDir(); + if (homeDir.startsWith(getDefaultHomeDir())) + // home dir is ok + return homeDir; + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) + // no need to migrate on Android 9 or earlier + return homeDir; + // Only ask to migrate once + String migrationPref = "legacy-storage-migration-done"; + if (prefs.getBoolean(migrationPref, false)) + return homeDir; + // Ask the user if he wants to migrate + AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); + dlgAlert.setMessage("The current Flycast home folder will be inaccessible in future versions.\n\n" + + "Do you want to move config and save files to a valid location?"); + dlgAlert.setTitle("Migrate Home"); + dlgAlert.setPositiveButton("Yes", + (dialog, id) -> BaseGLActivity.this.migrateHome(homeDir)); + dlgAlert.setNegativeButton("No", + (dialog, id) -> BaseGLActivity.this.finishCreation()); + dlgAlert.setIcon(android.R.drawable.ic_dialog_alert); + dlgAlert.setCancelable(false); + dlgAlert.create().show(); + // Don't ask again + prefs.edit().putBoolean(migrationPref, true).apply(); + + return null; + } + + private boolean migrationThreadCancelled = false; + private void migrateHome(String oldHome) + { + File source = new File(oldHome); + File dest = new File(getDefaultHomeDir()); + ProgressDialog progress = ProgressDialog.show(this, "Migrating", "Moving files to their new home", + true, true, dialogInterface -> migrationThreadCancelled = true); + progress.show(); + + migrationThreadCancelled = false; + Thread thread = new Thread(new Runnable() { + private void moveFile(File file, File toDir) + { + //Log.d("flycast", "Moving " + file.getAbsolutePath() + " to " + toDir.getAbsolutePath()); + try { + File dest = new File(toDir, file.getName()); + // file.renameTo(dest) doesn't seem to work + FileInputStream in = new FileInputStream(file); + FileOutputStream out = new FileOutputStream(dest); + byte[] buf = new byte[8192]; + while (true) { + int len = in.read(buf); + if (len == -1) + break; + out.write(buf, 0, len); + } + out.close(); + in.close(); + file.delete(); + } catch (IOException e) { + Log.e("flycast", "Error moving " + file.getAbsolutePath(), e); + } + } + + private void moveDir(File from, File to) + { + //Log.d("flycast", "Moving dir " + from.getAbsolutePath() + " to " + to.getAbsolutePath()); + if (!from.exists()) + return; + File[] files = from.listFiles(); + if (files == null) { + Log.e("flycast", "Can't list content of " + from.getAbsolutePath()); + return; + } + for (File file : files) + { + if (migrationThreadCancelled) + break; + if (file.isFile()) + moveFile(file, to); + else if (file.isDirectory() && !file.getName().equals("boxart")) { + File subDir = new File(to, file.getName()); + subDir.mkdir(); + moveDir(file, subDir); + } + } + from.delete(); + } + + private void migrate() + { + moveFile(new File(source, "emu.cfg"), dest); + if (migrationThreadCancelled) + return; + File mappings = new File(dest, "mappings"); + mappings.mkdirs(); + moveDir(new File(source, "mappings"), mappings); + if (migrationThreadCancelled) + return; + File data = new File(dest, "data"); + data.mkdirs(); + moveDir(new File(source, "data"), data); + } + + @Override + public void run() + { + migrate(); + runOnUiThread(() -> { + prefs.edit().putString(Config.pref_home, getDefaultHomeDir()).apply(); + progress.dismiss(); + BaseGLActivity.this.finishCreation(); + }); + } + }); + thread.start(); + } + // Called from native code public void onGameStateChange(boolean started) { runOnUiThread(new Runnable() { diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/NativeGLActivity.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/NativeGLActivity.java index b62656235..86408aceb 100644 --- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/NativeGLActivity.java +++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/NativeGLActivity.java @@ -35,28 +35,36 @@ public final class NativeGLActivity extends BaseGLActivity { super.onCreate(savedInstanceState); + Log.i("flycast", "NativeGLActivity.onCreate done"); + } + + protected void finishCreation() + { + super.finishCreation(); // Create the actual GL view mView = new NativeGLView(this); mLayout = new RelativeLayout(this); mLayout.addView(mView); setContentView(mLayout); - Log.i("flycast", "NativeGLActivity.onCreate done"); + Log.i("flycast", "NativeGLActivity.finishCreation done"); } @Override protected void doPause() { - mView.pause(); + if (mView != null) + mView.pause(); } @Override protected void doResume() { - mView.resume(); + if (mView != null) + mView.resume(); } @Override public boolean isSurfaceReady() { - return mView.isSurfaceReady(); + return mView != null && mView.isSurfaceReady(); } // Called from native code diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/config/Config.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/config/Config.java index fc3ef8601..ab59afc5f 100644 --- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/config/Config.java +++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/config/Config.java @@ -2,5 +2,4 @@ package com.flycast.emulator.config; public class Config { public static final String pref_home = "home_directory"; - public static String git_issues = "https://github.com/flyinghead/flycast/issues/"; }