diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp
index 8514d4c07e6..b24f4a4c518 100644
--- a/backends/platform/android/android.cpp
+++ b/backends/platform/android/android.cpp
@@ -313,8 +313,12 @@ void OSystem_Android::initBackend() {
ConfMan.setBool("FM_high_quality", false);
ConfMan.setBool("FM_medium_quality", true);
- if (!ConfMan.hasKey("browser_lastpath") || (ConfMan.hasKey("browser_lastpath") && (ConfMan.get("browser_lastpath") == "/storage")))
- ConfMan.set("browser_lastpath", getenv("SDCARD"));
+
+ if (!ConfMan.hasKey("browser_lastpath")) {
+ // TODO remove the debug message eventually
+ LOGD("Setting Browser Lastpath to root");
+ ConfMan.set("browser_lastpath", "/");
+ }
if (ConfMan.hasKey("touchpad_mouse_mode"))
_touchpad_mode = ConfMan.getBool("touchpad_mouse_mode");
@@ -335,6 +339,9 @@ void OSystem_Android::initBackend() {
// screen. Passing the savepath in this way makes it stick
// (via ConfMan.registerDefault)
_savefileManager = new DefaultSaveFileManager(ConfMan.get("savepath"));
+ // TODO remove the debug message eventually
+ LOGD("Setting DefaultSaveFileManager path to: %s", ConfMan.get("savepath").c_str());
+
_mutexManager = new PthreadMutexManager();
_timerManager = new DefaultTimerManager();
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
index 20fa482b669..d434ed878bc 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
@@ -27,6 +27,11 @@ import android.widget.ImageView;
import android.widget.Toast;
import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
import java.util.List;
public class ScummVMActivity extends Activity {
@@ -35,6 +40,10 @@ public class ScummVMActivity extends Activity {
private static boolean _hoverAvailable;
private ClipboardManager _clipboard;
+ private File _configScummvmFile;
+ private File _actualScummVMDataDir;
+ private File _defaultScummVMSavesDir;
+
/**
* Id to identify an external storage read request.
@@ -206,39 +215,31 @@ public class ScummVMActivity extends Activity {
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_READ_EXT_STORAGE);
}
- // This is a common enough error that we should warn about it
- // explicitly.
- if (!Environment.getExternalStorageDirectory().canRead()) {
- new AlertDialog.Builder(this)
- .setTitle(R.string.no_sdcard_title)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(R.string.no_sdcard)
- .setNegativeButton(R.string.quit,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int which) {
- finish();
- }
- })
- .show();
-
- return;
- }
+ // REMOVED: Dialogue prompt with only option to Quit the app if !Environment.getExternalStorageDirectory().canRead()
SurfaceView main_surface = (SurfaceView)findViewById(R.id.main_surface);
main_surface.requestFocus();
- getFilesDir().mkdirs();
+ // REMOVED: Since getFilesDir() is guaranteed to exist, getFilesDir().mkdirs() might be related to crashes in Android version 9+ (Pie or above, API 28+)!
- // Store savegames on external storage if we can, which means they're
- // world-readable and don't get deleted on uninstall.
- String savePath = Environment.getExternalStorageDirectory() + "/ScummVM/Saves/";
- File saveDir = new File(savePath);
- saveDir.mkdirs();
- if (!saveDir.isDirectory()) {
- // If it doesn't work, resort to the internal app path.
- savePath = getDir("saves", Context.MODE_PRIVATE).getPath();
+ // REMOVED: Setting savePath to Environment.getExternalStorageDirectory() + "/ScummVM/Saves/"
+ // so that it will be in persistent external storage and not deleted on uninstall
+ // This has the issue for external storage being unavailable on some devices
+ // Is this persistence really important considering that Android does not really support it anymore
+ // Exceptions are:
+ // - shareable media files (images, audio, video)
+ // - files stored with Storage Access Framework (SAF) which requires user interaction with FilePicker)
+ // Original fallback was getDir()
+ // so app's internal space, which would be deleted on uninstall) set as WORLD_READABLE which is no longer supported in newer versions of Android API
+ // In newer APIs we can set that path as Context.MODE_PRIVATE which is the default - but will make the files inaccessible to other apps
+
+ //
+ // seekAndInitScummvmConfiguration() returns false if something went wrong when
+ // initializing configuration (or finding and using an old ini file) for ScummVM
+ if (!seekAndInitScummvmConfiguration()) {
+ Log.e(ScummVM.LOG_TAG, "Error while trying to find and/or initialize scummvm configuration file!");
+ // TODO error prompt (popup to user)
}
_clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
@@ -247,10 +248,10 @@ public class ScummVMActivity extends Activity {
_scummvm = new MyScummVM(main_surface.getHolder());
_scummvm.setArgs(new String[] {
- "ScummVM",
- "--config=" + getFileStreamPath("scummvmrc").getPath(),
- "--path=" + Environment.getExternalStorageDirectory().getPath(),
- "--savepath=" + savePath
+ "ScummVM",
+ "--config=" + _configScummvmFile.getPath(),
+ "--path=" + _actualScummVMDataDir.getPath(),
+ "--savepath=" + _defaultScummVMSavesDir.getPath()
});
Log.d(ScummVM.LOG_TAG, "Hover available: " + _hoverAvailable);
@@ -419,4 +420,139 @@ public class ScummVMActivity extends Activity {
sendBroadcast(intent);
}
}
+
+ // Auxilliary function to overwrite a file (used for overwriting the scummvm.ini file with an existing other one)
+ private static void copyFileUsingStream(File source, File dest) throws IOException {
+ InputStream is = null;
+ OutputStream os = null;
+ try {
+ is = new FileInputStream(source);
+ os = new FileOutputStream(dest);
+ byte[] buffer = new byte[1024];
+ int length;
+ while ((length = is.read(buffer)) > 0) {
+ os.write(buffer, 0, length);
+ }
+ } finally {
+ is.close();
+ os.close();
+ }
+ }
+
+ private boolean seekAndInitScummvmConfiguration() {
+ boolean retVal = false;
+
+ _actualScummVMDataDir = getExternalFilesDir(null);
+ if (_actualScummVMDataDir == null || !_actualScummVMDataDir.canRead()) {
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.no_external_files_dir_access_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.no_external_files_dir_access)
+ .setNegativeButton(R.string.quit,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+ })
+ .show();
+ retVal = false;
+ return retVal;
+ }
+
+ Log.d(ScummVM.LOG_TAG, "Base ScummVM data folder is: " + _actualScummVMDataDir.getPath());
+
+ File[] extfiles = _actualScummVMDataDir.listFiles();
+ Log.d(ScummVM.LOG_TAG, "Size: "+ extfiles.length);
+ for (int i = 0; i < extfiles.length; i++) {
+ Log.d(ScummVM.LOG_TAG, "FileName:" + extfiles[i].getName());
+ }
+
+ File externalScummVMConfigDir = new File(_actualScummVMDataDir, ".config/scummvm");
+ if (externalScummVMConfigDir.mkdirs()) {
+ Log.d(ScummVM.LOG_TAG, "Created ScummVM Config path: " + externalScummVMConfigDir.getPath());
+ } else if (externalScummVMConfigDir.isDirectory()) {
+ Log.d(ScummVM.LOG_TAG, "ScummVM Config path already exists: " + externalScummVMConfigDir.getPath());
+ } else {
+ Log.e(ScummVM.LOG_TAG, "Could not create folder for ScummVM Config path: " + externalScummVMConfigDir.getPath());
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.no_config_file_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.no_config_file)
+ .setNegativeButton(R.string.quit,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+ })
+ .show();
+ }
+
+ _configScummvmFile = new File(_actualScummVMDataDir, "scummvm.ini");
+ try {
+ if (!_configScummvmFile.createNewFile()) {
+ Log.d(ScummVM.LOG_TAG, "ScummVM Config file already exists!");
+ Log.d(ScummVM.LOG_TAG, "Existing Scummvm INI: " + _configScummvmFile.getPath());
+ } else {
+ Log.d(ScummVM.LOG_TAG, "ScummVM Config file was created!");
+ Log.d(ScummVM.LOG_TAG, "New Scummvm INI: " + _configScummvmFile.getPath());
+ // if there was an old scummvmrc file (old config file), then copy that over the empty new scummvm.ini
+ File oldScummVMconfig = getFileStreamPath("scummvmrc");
+ if (!oldScummVMconfig.exists()) {
+ Log.d(ScummVM.LOG_TAG, "Old config Scummvm file was not found!");
+ } else {
+ Log.d(ScummVM.LOG_TAG, "Old config Scummvm file was found!");
+ copyFileUsingStream(oldScummVMconfig, _configScummvmFile);
+ Log.d(ScummVM.LOG_TAG, "Old config Scummvm file overwrites the new (empty) scummvm.ini");
+ }
+ }
+ } catch(Exception e) {
+ e.printStackTrace();
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.no_config_file_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.no_config_file)
+ .setNegativeButton(R.string.quit,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+ })
+ .show();
+ }
+
+
+ // Set global savepath
+ // TODO what if the old save-game path is no longer accessible (due to missing SD card, or newer Android API/OS version more strict restrictions)
+ // TODO changing the save path to this, app specific location we should consider:
+ // - moving or copying old save files from old location (if accessible)
+ // - allowing the user to set a user-defined location via Storage Access Framework, that would override this location!
+ // TODO This would override/ overwrite(?) actual old save-game path stored in the config file!
+ //
+ // By default shoose to store savegames on app's external storage, which means they're accessible by other apps BUT will get deleted on uninstall!
+ //
+ _defaultScummVMSavesDir = new File(_actualScummVMDataDir, "saves");
+ // TODO what about old save paths from plain android port? do we favor them?
+ if (_defaultScummVMSavesDir.mkdirs()) {
+ Log.d(ScummVM.LOG_TAG, "Created ScummVM saves path: " + _defaultScummVMSavesDir.getPath());
+ } else if (_defaultScummVMSavesDir.isDirectory()) {
+ Log.d(ScummVM.LOG_TAG, "ScummVM saves path already exists: " + _defaultScummVMSavesDir.getPath());
+ } else {
+ Log.e(ScummVM.LOG_TAG, "Could not create folder for ScummVM saves path: " + _defaultScummVMSavesDir.getPath());
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.no_config_file_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.no_config_file)
+ .setNegativeButton(R.string.quit,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+ })
+ .show();
+ }
+
+ retVal = true;
+ return retVal;
+ }
+
}
diff --git a/dists/android/res/values/strings.xml b/dists/android/res/values/strings.xml
index e06509d3ed8..830a30fe131 100644
--- a/dists/android/res/values/strings.xml
+++ b/dists/android/res/values/strings.xml
@@ -14,6 +14,14 @@
Unable to read your SD card. This usually
means you still have it mounted on your PC. Unmount, reinsert,
whatever and then try again.
+ External storage error
+ Unable to access external storage
+ to retrieve ScummVM config info! Please grant storage access permissions to
+ the ScummVM app, in order to function properly!
+ Config File Error
+ Unable to read ScummVM config file or create a new one!
+ Save Path Error
+ Unable to create or access default save path!
No plugins found
ScummVM requires at least one game
engine to be useful. Engines are available as separate plugin