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