From 4ac31d5481f87fb02a1741f9db024aafefeb0f15 Mon Sep 17 00:00:00 2001 From: antoniou Date: Sun, 1 Aug 2021 15:53:12 +0300 Subject: [PATCH] ANDROID: Use SAF if a directory is not writeable --- backends/fs/posix/posix-fs.cpp | 9 +++- backends/platform/android/jni-android.cpp | 21 ++++++++- backends/platform/android/jni-android.h | 2 + .../android/org/scummvm/scummvm/ScummVM.java | 1 + .../org/scummvm/scummvm/ScummVMActivity.java | 46 +++++++++++++++++-- 5 files changed, 72 insertions(+), 7 deletions(-) diff --git a/backends/fs/posix/posix-fs.cpp b/backends/fs/posix/posix-fs.cpp index 9a978508201..c357fede00d 100644 --- a/backends/fs/posix/posix-fs.cpp +++ b/backends/fs/posix/posix-fs.cpp @@ -68,7 +68,14 @@ bool POSIXFilesystemNode::isReadable() const { } bool POSIXFilesystemNode::isWritable() const { - return access(_path.c_str(), W_OK) == 0; + bool retVal = access(_path.c_str(), W_OK) == 0; +#if defined(ANDROID_PLAIN_PORT) + if (!retVal) { + // Update return value if going through Android's SAF grants the permission + retVal = JNI::isDirectoryWritableWithSAF(_path); + } +#endif // ANDROID_PLAIN_PORT + return retVal; } void POSIXFilesystemNode::setFlags() { diff --git a/backends/platform/android/jni-android.cpp b/backends/platform/android/jni-android.cpp index 1d0e42ec994..38e6d074ea0 100644 --- a/backends/platform/android/jni-android.cpp +++ b/backends/platform/android/jni-android.cpp @@ -91,6 +91,7 @@ jmethodID JNI::_MID_deinitSurface = 0; jmethodID JNI::_MID_createDirectoryWithSAF = 0; jmethodID JNI::_MID_createFileWithSAF = 0; jmethodID JNI::_MID_closeFileWithSAF = 0; +jmethodID JNI::_MID_isDirectoryWritableWithSAF = 0; jmethodID JNI::_MID_EGL10_eglSwapBuffers = 0; @@ -561,6 +562,7 @@ void JNI::create(JNIEnv *env, jobject self, jobject asset_manager, FIND_METHOD(, createDirectoryWithSAF, "(Ljava/lang/String;)Z"); FIND_METHOD(, createFileWithSAF, "(Ljava/lang/String;)Ljava/lang/String;"); FIND_METHOD(, closeFileWithSAF, "(Ljava/lang/String;)V"); + FIND_METHOD(, isDirectoryWritableWithSAF, "(Ljava/lang/String;)Z"); _jobj_egl = env->NewGlobalRef(egl); _jobj_egl_display = env->NewGlobalRef(egl_display); @@ -814,7 +816,6 @@ Common::U32String JNI::createFileWithSAF(const Common::String &filePath) { Common::U32String hackyFilenameStr = convertFromJString(env, hackyFilenameJSTR); - //LOGD("JNI - _MID_createFileWithSAF returned %s", hackyFilenameStr.c_str()); env->DeleteLocalRef(hackyFilenameJSTR); return hackyFilenameStr; @@ -836,4 +837,22 @@ void JNI::closeFileWithSAF(const Common::String &hackyFilename) { } +bool JNI::isDirectoryWritableWithSAF(const Common::String &dirPath) { + JNIEnv *env = JNI::getEnv(); + jstring javaDirPath = env->NewStringUTF(dirPath.c_str()); + + bool isWritable = env->CallBooleanMethod(_jobj, _MID_isDirectoryWritableWithSAF, javaDirPath); + + if (env->ExceptionCheck()) { + LOGE("JNI - Failed to check if directory is writable SAF enhanced method"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + isWritable = false; + } + + return isWritable; + +} + #endif diff --git a/backends/platform/android/jni-android.h b/backends/platform/android/jni-android.h index 17a62aefa18..51908899edf 100644 --- a/backends/platform/android/jni-android.h +++ b/backends/platform/android/jni-android.h @@ -87,6 +87,7 @@ public: static bool createDirectoryWithSAF(const Common::String &dirPath); static Common::U32String createFileWithSAF(const Common::String &filePath); static void closeFileWithSAF(const Common::String &hackyFilename); + static bool isDirectoryWritableWithSAF(const Common::String &dirPath); private: static JavaVM *_vm; @@ -119,6 +120,7 @@ private: static jmethodID _MID_createDirectoryWithSAF; static jmethodID _MID_createFileWithSAF; static jmethodID _MID_closeFileWithSAF; + static jmethodID _MID_isDirectoryWritableWithSAF; static jmethodID _MID_EGL10_eglSwapBuffers; diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVM.java b/backends/platform/android/org/scummvm/scummvm/ScummVM.java index 0852ddeb88f..d23e1ee673a 100644 --- a/backends/platform/android/org/scummvm/scummvm/ScummVM.java +++ b/backends/platform/android/org/scummvm/scummvm/ScummVM.java @@ -74,6 +74,7 @@ public abstract class ScummVM implements SurfaceHolder.Callback, Runnable { abstract protected boolean createDirectoryWithSAF(String dirPath); abstract protected String createFileWithSAF(String filePath); abstract protected void closeFileWithSAF(String hackyFilename); + abstract protected boolean isDirectoryWritableWithSAF(String dirPath); public ScummVM(AssetManager asset_manager, SurfaceHolder holder, final MyScummVMDestroyedCallback scummVMDestroyedCallback) { _asset_manager = asset_manager; diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java index 776e134dc12..688381898d3 100644 --- a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java +++ b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java @@ -417,19 +417,19 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis // TODO - "Swipe" behavior does not seem to work currently. Should we support it? public void swipeLeft() { - //Log.d(ScummVM.LOG_TAG, "SHOW KEYBOARD - 001 - swipeLeft"); +// Log.d(ScummVM.LOG_TAG, "SHOW KEYBOARD - 001 - swipeLeft"); } public void swipeRight() { - //Log.d(ScummVM.LOG_TAG, "SHOW KEYBOARD - 001 - swipeRight" ); +// Log.d(ScummVM.LOG_TAG, "SHOW KEYBOARD - 001 - swipeRight" ); } public void swipeDown() { - //Log.d(ScummVM.LOG_TAG, "SHOW KEYBOARD - 001 - swipeDown" ); +// Log.d(ScummVM.LOG_TAG, "SHOW KEYBOARD - 001 - swipeDown" ); } public void swipeUp() { - //Log.d(ScummVM.LOG_TAG, "SHOW KEYBOARD - 001 - swipeUp "); +// Log.d(ScummVM.LOG_TAG, "SHOW KEYBOARD - 001 - swipeUp "); } public void onKey(int key, int[] keysAround) { // Log.d(ScummVM.LOG_TAG, "SHOW KEYBOARD - 001 - onKey key: " + key ); @@ -583,7 +583,6 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis } }; - private class MyScummVM extends ScummVM { public MyScummVM(SurfaceHolder holder, final MyScummVMDestroyedCallback destroyedCallback) { @@ -793,6 +792,36 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis return retRes[0]; } + + // This is a simplified version of createDirectoryWithSAF + // TODO Maybe we could merge isDirectoryWritableWithSAF() with createDirectoryWithSAF() using an extra argument parameter + @Override + protected boolean isDirectoryWritableWithSAF(String dirPath) { + final boolean[] retRes = {false}; + + Log.d(ScummVM.LOG_TAG, "Check if folder writable: " + dirPath); + File folderToCheck = new File (dirPath); + if (folderToCheck.canWrite()) { + Log.d(ScummVM.LOG_TAG, "This path has write permission!" + dirPath); + } else { + Log.d(ScummVM.LOG_TAG, "Trying to get write access with SAF"); + if (getStorageAccessFrameworkTreeUri() == null) { + requestStorageAccessFramework(dirPath); + } else { + Log.d(ScummVM.LOG_TAG, "Already requested Storage Access (Storage Access Framework) in the past (share prefs saved)!"); + } + } + + if (canWriteFile(folderToCheck, true)) { + Log.d(ScummVM.LOG_TAG, "(post SAF request) Writing is possible for this directory node"); + retRes[0] = true; + } else { + Log.d(ScummVM.LOG_TAG, "(post SAF request) Error - writing is still not possible for this directory node"); + } + + return retRes[0]; + } + @Override protected String createFileWithSAF(String filePath) { final String[] retResStr = {""}; @@ -2158,6 +2187,7 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis _scummvm.displayMessageOnOSD(getString(R.string.saf_request_prompt) + dirPathSample); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + // Directory picker Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION @@ -2298,14 +2328,20 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis } catch (Exception ignored) { originalDirectory = true; } + Uri treeUri; if ((treeUri = getStorageAccessFrameworkTreeUri()) == null) { return null; } + DocumentFile dof = DocumentFile.fromTreeUri(getApplicationContext(), treeUri); if (originalDirectory) { return dof; } + + // Important note: We cannot assume that anything sent here is a relative path on top of the *ONLY* SAF "root" path + // since the the user could select another SD Card (from multiple inserted or replaces the current one and inserts another) + // TODO Can we translate our path string "/storage/XXXX-XXXXX/folder/doc.ext' a content URI? or a document URI? String[] parts = relPath.split("\\/"); for (int i = 0; i < parts.length; i++) { DocumentFile nextDof = dof.findFile(parts[i]);