[Scummvm-git-logs] scummvm master -> 250206bd00096617c3aa549ff740ea44562347de

antoniou79 a.antoniou79 at gmail.com
Sun Aug 1 13:11:35 UTC 2021


This automated email contains information about 2 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
4ac31d5481 ANDROID: Use SAF if a directory is not writeable
250206bd00 ANDROID: Add a checkbox for a revoke SAF permissions button


Commit: 4ac31d5481f87fb02a1741f9db024aafefeb0f15
    https://github.com/scummvm/scummvm/commit/4ac31d5481f87fb02a1741f9db024aafefeb0f15
Author: antoniou (a.antoniou79 at gmail.com)
Date: 2021-08-01T16:10:54+03:00

Commit Message:
ANDROID: Use SAF if a directory is not writeable

Changed paths:
    backends/fs/posix/posix-fs.cpp
    backends/platform/android/jni-android.cpp
    backends/platform/android/jni-android.h
    backends/platform/android/org/scummvm/scummvm/ScummVM.java
    backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java


diff --git a/backends/fs/posix/posix-fs.cpp b/backends/fs/posix/posix-fs.cpp
index 9a97850820..c357fede00 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 1d0e42ec99..38e6d074ea 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 17a62aefa1..51908899ed 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 0852ddeb88..d23e1ee673 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 776e134dc1..688381898d 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]);


Commit: 250206bd00096617c3aa549ff740ea44562347de
    https://github.com/scummvm/scummvm/commit/250206bd00096617c3aa549ff740ea44562347de
Author: antoniou (a.antoniou79 at gmail.com)
Date: 2021-08-01T16:10:54+03:00

Commit Message:
ANDROID: Add a checkbox for a revoke SAF permissions button

Themes were updated to version SCUMMVM_THEME_VERSION_STR "SCUMMVM_STX0.8.48"

Changed paths:
  A dists/android/res/drawable/ic_lock_icon.xml
    backends/platform/android/jni-android.cpp
    backends/platform/android/jni-android.h
    backends/platform/android/options.cpp
    backends/platform/android/org/scummvm/scummvm/ScummVM.java
    backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
    common/system.h
    dists/android/res/values/strings.xml
    gui/ThemeEngine.h
    gui/options.cpp
    gui/options.h
    gui/themes/default.inc
    gui/themes/residualvm.zip
    gui/themes/residualvm/THEMERC
    gui/themes/residualvm/residualvm_layout.stx
    gui/themes/residualvm/residualvm_layout_lowres.stx
    gui/themes/scummclassic.zip
    gui/themes/scummclassic/THEMERC
    gui/themes/scummmodern.zip
    gui/themes/scummmodern/THEMERC
    gui/themes/scummmodern/scummmodern_layout.stx
    gui/themes/scummmodern/scummmodern_layout_lowres.stx
    gui/themes/scummremastered.zip
    gui/themes/scummremastered/THEMERC
    gui/themes/scummremastered/remastered_layout.stx
    gui/themes/scummremastered/remastered_layout_lowres.stx


diff --git a/backends/platform/android/jni-android.cpp b/backends/platform/android/jni-android.cpp
index 38e6d074ea..7baa3683dd 100644
--- a/backends/platform/android/jni-android.cpp
+++ b/backends/platform/android/jni-android.cpp
@@ -84,6 +84,7 @@ jmethodID JNI::_MID_isConnectionLimited = 0;
 jmethodID JNI::_MID_setWindowCaption = 0;
 jmethodID JNI::_MID_showVirtualKeyboard = 0;
 jmethodID JNI::_MID_showKeyboardControl = 0;
+jmethodID JNI::_MID_showSAFRevokePermsControl = 0;
 jmethodID JNI::_MID_getSysArchives = 0;
 jmethodID JNI::_MID_getAllStorageLocations = 0;
 jmethodID JNI::_MID_initSurface = 0;
@@ -385,6 +386,19 @@ void JNI::showKeyboardControl(bool enable) {
 	}
 }
 
+void JNI::showSAFRevokePermsControl(bool enable) {
+	JNIEnv *env = JNI::getEnv();
+
+	env->CallVoidMethod(_jobj, _MID_showSAFRevokePermsControl, enable);
+
+	if (env->ExceptionCheck()) {
+		LOGE("Error trying to show the revoke SAF permissions button");
+
+		env->ExceptionDescribe();
+		env->ExceptionClear();
+	}
+}
+
 // The following adds assets folder to search set.
 // However searching and retrieving from "assets" on Android this is slow
 // so we also make sure to add the "path" directory, with a higher priority
@@ -555,6 +569,7 @@ void JNI::create(JNIEnv *env, jobject self, jobject asset_manager,
 	FIND_METHOD(, isConnectionLimited, "()Z");
 	FIND_METHOD(, showVirtualKeyboard, "(Z)V");
 	FIND_METHOD(, showKeyboardControl, "(Z)V");
+	FIND_METHOD(, showSAFRevokePermsControl, "(Z)V");
 	FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;");
 	FIND_METHOD(, getAllStorageLocations, "()[Ljava/lang/String;");
 	FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;");
@@ -856,3 +871,4 @@ bool JNI::isDirectoryWritableWithSAF(const Common::String &dirPath) {
 }
 
 #endif
+
diff --git a/backends/platform/android/jni-android.h b/backends/platform/android/jni-android.h
index 51908899ed..81af274976 100644
--- a/backends/platform/android/jni-android.h
+++ b/backends/platform/android/jni-android.h
@@ -68,6 +68,7 @@ public:
 	static bool isConnectionLimited();
 	static void showVirtualKeyboard(bool enable);
 	static void showKeyboardControl(bool enable);
+	static void showSAFRevokePermsControl(bool enable);
 	static void addSysArchivesToSearchSet(Common::SearchSet &s, int priority);
 
 	static inline bool haveSurface();
@@ -113,6 +114,7 @@ private:
 	static jmethodID _MID_setWindowCaption;
 	static jmethodID _MID_showVirtualKeyboard;
 	static jmethodID _MID_showKeyboardControl;
+	static jmethodID _MID_showSAFRevokePermsControl;
 	static jmethodID _MID_getSysArchives;
 	static jmethodID _MID_getAllStorageLocations;
 	static jmethodID _MID_initSurface;
diff --git a/backends/platform/android/options.cpp b/backends/platform/android/options.cpp
index 2ed2225feb..1839517506 100644
--- a/backends/platform/android/options.cpp
+++ b/backends/platform/android/options.cpp
@@ -62,6 +62,7 @@ private:
 
 	GUI::CheckboxWidget *_onscreenCheckbox;
 	GUI::CheckboxWidget *_touchpadCheckbox;
+	GUI::CheckboxWidget *_onscreenSAFRevokeCheckbox;
 
 	bool _enabled;
 };
@@ -71,6 +72,7 @@ AndroidOptionsWidget::AndroidOptionsWidget(GuiObject *boss, const Common::String
 
 	_onscreenCheckbox = new GUI::CheckboxWidget(widgetsBoss(), "AndroidOptionsDialog.OnScreenControl", _("Show On-screen control"));
 	_touchpadCheckbox = new GUI::CheckboxWidget(widgetsBoss(), "AndroidOptionsDialog.TouchpadMode", _("Touchpad mouse mode"));
+	_onscreenSAFRevokeCheckbox = new GUI::CheckboxWidget(widgetsBoss(), "AndroidOptionsDialog.SAFRevokePermsControl", _("Show SAF revoke permissions overlay button"));
 }
 
 AndroidOptionsWidget::~AndroidOptionsWidget() {
@@ -82,6 +84,7 @@ void AndroidOptionsWidget::defineLayout(GUI::ThemeEval &layouts, const Common::S
 	            .addPadding(0, 0, 0, 0)
 	            .addWidget("OnScreenControl", "Checkbox")
 	            .addWidget("TouchpadMode", "Checkbox")
+	            .addWidget("SAFRevokePermsControl", "Checkbox")
 	        .closeLayout()
 	    .closeDialog();
 }
@@ -89,15 +92,18 @@ void AndroidOptionsWidget::defineLayout(GUI::ThemeEval &layouts, const Common::S
 void AndroidOptionsWidget::load() {
 	_onscreenCheckbox->setState(ConfMan.getBool("onscreen_control", _domain));
 	_touchpadCheckbox->setState(ConfMan.getBool("touchpad_mouse_mode", _domain));
+	_onscreenSAFRevokeCheckbox->setState(ConfMan.getBool("onscreen_saf_revoke_btn", _domain));
 }
 
 bool AndroidOptionsWidget::save() {
 	if (_enabled) {
 		ConfMan.setBool("onscreen_control", _onscreenCheckbox->getState(), _domain);
 		ConfMan.setBool("touchpad_mouse_mode", _touchpadCheckbox->getState(), _domain);
+		ConfMan.setBool("onscreen_saf_revoke_btn", _onscreenSAFRevokeCheckbox->getState(), _domain);
 	} else {
 		ConfMan.removeKey("onscreen_control", _domain);
 		ConfMan.removeKey("touchpad_mouse_mode", _domain);
+		ConfMan.removeKey("onscreen_saf_revoke_btn", _domain);
 	}
 
 	return true;
@@ -105,7 +111,8 @@ bool AndroidOptionsWidget::save() {
 
 bool AndroidOptionsWidget::hasKeys() {
 	return ConfMan.hasKey("onscreen_control", _domain) ||
-	       ConfMan.hasKey("touchpad_mouse_mode", _domain);
+	       ConfMan.hasKey("touchpad_mouse_mode", _domain) ||
+	       ConfMan.hasKey("onscreen_saf_revoke_btn", _domain);
 }
 
 void AndroidOptionsWidget::setEnabled(bool e) {
@@ -113,6 +120,7 @@ void AndroidOptionsWidget::setEnabled(bool e) {
 
 	_onscreenCheckbox->setEnabled(e);
 	_touchpadCheckbox->setEnabled(e);
+	_onscreenSAFRevokeCheckbox->setEnabled(e);
 }
 
 
@@ -123,9 +131,11 @@ GUI::OptionsContainerWidget *OSystem_Android::buildBackendOptionsWidget(GUI::Gui
 void OSystem_Android::registerDefaultSettings(const Common::String &target) const {
 	ConfMan.registerDefault("onscreen_control", true);
 	ConfMan.registerDefault("touchpad_mouse_mode", true);
+	ConfMan.registerDefault("onscreen_saf_revoke_btn", false);
 }
 
 void OSystem_Android::applyBackendSettings() {
 	JNI::showKeyboardControl(ConfMan.getBool("onscreen_control"));
+	JNI::showSAFRevokePermsControl(ConfMan.getBool("onscreen_saf_revoke_btn"));
 	_touchpad_mode = ConfMan.getBool("touchpad_mouse_mode");
 }
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVM.java b/backends/platform/android/org/scummvm/scummvm/ScummVM.java
index d23e1ee673..73f6f5e439 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVM.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVM.java
@@ -68,6 +68,7 @@ public abstract class ScummVM implements SurfaceHolder.Callback, Runnable {
 	abstract protected void setWindowCaption(String caption);
 	abstract protected void showVirtualKeyboard(boolean enable);
 	abstract protected void showKeyboardControl(boolean enable);
+	abstract protected void showSAFRevokePermsControl(boolean enable);
 	abstract protected String[] getSysArchives();
 	abstract protected String[] getAllStorageLocations();
 	abstract protected String[] getAllStorageLocationsNoPermissionRequest();
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
index 688381898d..b472540ac8 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
@@ -117,6 +117,7 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 	private EditableSurfaceView _main_surface = null;
 	private ImageView _toggleKeyboardBtnIcon = null;
 	private ImageView _openMenuBtnIcon = null;
+	private ImageView _revokeSafPermissionsBtnIcon = null;
 
 	public View _screenKeyboard = null;
 	static boolean keyboardWithoutTextInputShown = false;
@@ -571,7 +572,6 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 		}
 	};
 
-
 	public final View.OnClickListener menuBtnOnClickListener = new View.OnClickListener() {
 		@Override
 		public void onClick(View v) {
@@ -583,6 +583,18 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 		}
 	};
 
+	public final View.OnClickListener revokeSafPermissionsBtnOnClickListener = new View.OnClickListener() {
+		@Override
+		public void onClick(View v) {
+			runOnUiThread(new Runnable() {
+				public void run() {
+					clearStorageAccessFrameworkTreeUri();
+					_scummvm.displayMessageOnOSD(getString(R.string.saf_revoke_done));
+				}
+			});
+		}
+	};
+
 	private class MyScummVM extends ScummVM {
 
 		public MyScummVM(SurfaceHolder holder, final MyScummVMDestroyedCallback destroyedCallback) {
@@ -687,6 +699,15 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 			});
 		}
 
+		@Override
+		protected void showSAFRevokePermsControl(final boolean enable) {
+			runOnUiThread(new Runnable() {
+				public void run() {
+					showSAFRevokePermissionsBtnIcon(enable);
+				}
+			});
+		}
+
 		@Override
 		protected String[] getSysArchives() {
 			Log.d(ScummVM.LOG_TAG, "Adding to Search Archive: " + _actualScummVMDataDir.getPath());
@@ -941,6 +962,11 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 		buttonLayout.addView(_openMenuBtnIcon, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT));
 		buttonLayout.bringChildToFront(_openMenuBtnIcon);
 
+		_revokeSafPermissionsBtnIcon = new ImageView(this);
+		_revokeSafPermissionsBtnIcon.setImageResource(R.drawable.ic_lock_icon);
+		buttonLayout.addView(_revokeSafPermissionsBtnIcon, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT));
+		buttonLayout.bringChildToFront(_revokeSafPermissionsBtnIcon);
+
 		_main_surface.setFocusable(true);
 		_main_surface.setFocusableInTouchMode(true);
 		_main_surface.requestFocus();
@@ -1030,6 +1056,7 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 			//findViewById(R.id.show_keyboard).setOnClickListener(keyboardBtnOnClickListener);
 			_toggleKeyboardBtnIcon.setOnClickListener(keyboardBtnOnClickListener);
 			_openMenuBtnIcon.setOnClickListener(menuBtnOnClickListener);
+			_revokeSafPermissionsBtnIcon.setOnClickListener(revokeSafPermissionsBtnOnClickListener);
 
 			// Keyboard visibility listener - mainly to hide system UI if keyboard is shown and we return from Suspend to the Activity
 			setKeyboardVisibilityListener(this);
@@ -1131,6 +1158,7 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 			hideScreenKeyboard();
 		}
 		showToggleKeyboardBtnIcon(false);
+		showSAFRevokePermissionsBtnIcon(false);
 	}
 
 
@@ -1298,6 +1326,19 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 		}
 	}
 
+	// Show or hide the semi-transparent overlay button
+	// for revoking SAF permissions
+	// This is independent of the toggle keyboard icon and menu icon (which appear together currently in showToggleKeyboardBtnIcon())
+	private void showSAFRevokePermissionsBtnIcon(boolean show) {
+		if (_revokeSafPermissionsBtnIcon != null ) {
+			if (show) {
+				_revokeSafPermissionsBtnIcon.setVisibility(View.VISIBLE);
+			} else {
+				_revokeSafPermissionsBtnIcon.setVisibility(View.GONE);
+			}
+		}
+	}
+
 	// Listener to check for keyboard visibility changes
 	// https://stackoverflow.com/a/36259261
 	private void setKeyboardVisibilityListener(final OnKeyboardVisibilityListener onKeyboardVisibilityListener) {
@@ -2217,6 +2258,26 @@ public class ScummVMActivity extends Activity implements OnKeyboardVisibilityLis
 		return null;
 	}
 
+	// A method to revoke SAF granted stored permissions
+	// TODO We need a button or setting to trigger this on user's demand
+	public void clearStorageAccessFrameworkTreeUri() {
+		if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
+			return;
+		}
+
+		Uri treeUri;
+		if ((treeUri = getStorageAccessFrameworkTreeUri()) == null) {
+			return;
+		}
+
+		// revoke SAF permission AND clear the pertinent SharedPreferences key
+		getContentResolver().releasePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+		SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
+		SharedPreferences.Editor editor = sharedPref.edit();
+		editor.remove(getString(R.string.preference_saf_tree_key));
+		editor.apply();
+	}
+
 	public File getStorageRootFolder(final File file) {
 		String filepath;
 		try {
diff --git a/common/system.h b/common/system.h
index 26c6963a44..72c4cfd9ee 100644
--- a/common/system.h
+++ b/common/system.h
@@ -503,7 +503,12 @@ public:
 		/**
 		* For platforms that should not have a Quit button.
 		*/
-		kFeatureNoQuit
+		kFeatureNoQuit,
+
+		/**
+		* Android specific button toggle to revoke storage permissions
+		*/
+		kFeatureSAFRevokePermsControl
 	};
 
 	/**
diff --git a/dists/android/res/drawable/ic_lock_icon.xml b/dists/android/res/drawable/ic_lock_icon.xml
new file mode 100644
index 0000000000..8ed59fcf73
--- /dev/null
+++ b/dists/android/res/drawable/ic_lock_icon.xml
@@ -0,0 +1,11 @@
+<vector android:alpha="0.5" android:height="48dp"
+    android:viewportHeight="48" android:viewportWidth="48"
+    android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#ffffff" android:pathData="M10,20h30v25h-30z"/>
+    <path android:fillColor="#ffffff"
+        android:pathData="M24.1836,-0.1387C22.4171,-0.1698 20.6862,0.2104 19.0703,1.0039 16.2909,2.3688 14.0478,4.9097 12.5781,8.3594c-0.2786,0.654 -0.7844,2.1021 -0.9727,2.7871l-0.0918,0.3379 2.2363,-0.0117 2.2383,-0.0117 0.2461,-0.5371c1.048,-2.2753 2.5856,-4.0879 4.4082,-5.1992 0.8166,-0.4979 1.7899,-0.8821 2.7891,-1.0977 0.4245,-0.0916 0.6295,-0.1039 1.5391,-0.1055 0.9158,-0.0016 1.115,0.0113 1.5801,0.1055 1.4601,0.2957 2.8223,0.9677 4.0605,2.0039 1.635,1.3683 2.9994,3.5111 3.832,6.0176 0.5823,1.7528 0.9676,3.981 1.0391,5.998l0.0313,0.8496h1.9707,1.9727v-0.4082c-0.0006,-1.2624 -0.2448,-3.3084 -0.584,-4.8867C37.5886,8.2233 34.1417,3.4317 29.5117,1.1875 27.7518,0.3344 25.9501,-0.1075 24.1836,-0.1387Z" android:strokeWidth="0.0465853"/>
+    <path android:fillColor="#ffffff"
+        android:pathData="M10.0624,32.5165V20.0317h14.9073,14.9073v12.4849,12.4849h-14.9073,-14.9073z" android:strokeWidth="0.186341"/>
+    <path android:fillColor="#ffffff"
+        android:pathData="M35.5103,17.8442C35.1329,13.1932 33.6772,9.5353 31.2341,7.0994 27.5554,3.4314 22.1709,3.54 18.5722,7.3548 17.8965,8.0711 16.9975,9.2878 16.5745,10.0586l-0.7691,1.4014H13.7724c-1.705,0 -2.0329,-0.0462 -2.0329,-0.2865 0,-0.5967 1.3591,-3.7746 2.1735,-5.082 1.7542,-2.8164 3.8975,-4.5919 6.719,-5.5661 2.6996,-0.9321 6.0179,-0.6265 8.8984,0.8194 3.4307,1.722 6.4099,5.0833 8.0319,9.0617 1.0465,2.5667 1.7557,5.8582 1.7557,8.1479v0.9182H37.4802,35.6424Z" android:strokeWidth="0.186341"/>
+</vector>
diff --git a/dists/android/res/values/strings.xml b/dists/android/res/values/strings.xml
index 7a9cb9d05c..08eb96d360 100644
--- a/dists/android/res/values/strings.xml
+++ b/dists/android/res/values/strings.xml
@@ -55,5 +55,6 @@
 	<string name="customkeyboardview_popup_close">Close popup</string>
 
 	<string name="saf_request_prompt">Please select the *root* of your external (physical) SD card. This is required for ScummVM to access this path: </string>
+	<string name="saf_revoke_done">Storage Access Framework Permissions for ScummVM were revoked!</string>
 	<string name="preference_saf_tree_key" translatable="false">pref_key__saf_tree_uri</string>
 </resources>
diff --git a/gui/ThemeEngine.h b/gui/ThemeEngine.h
index 38cd57b98c..77af3405de 100644
--- a/gui/ThemeEngine.h
+++ b/gui/ThemeEngine.h
@@ -37,7 +37,7 @@
 #include "graphics/pixelformat.h"
 
 
-#define SCUMMVM_THEME_VERSION_STR "SCUMMVM_STX0.8.47"
+#define SCUMMVM_THEME_VERSION_STR "SCUMMVM_STX0.8.48"
 
 class OSystem;
 
diff --git a/gui/options.cpp b/gui/options.cpp
index a3e28d6a6b..2401994592 100644
--- a/gui/options.cpp
+++ b/gui/options.cpp
@@ -167,6 +167,7 @@ void OptionsDialog::init() {
 	_enableControlSettings = false;
 	_onscreenCheckbox = nullptr;
 	_touchpadCheckbox = nullptr;
+	_onscreenSAFRevokeCheckbox = nullptr;
 	_swapMenuAndBackBtnsCheckbox = nullptr;
 	_kbdMouseSpeedDesc = nullptr;
 	_kbdMouseSpeedSlider = nullptr;
@@ -262,6 +263,13 @@ void OptionsDialog::build() {
 				_onscreenCheckbox->setState(onscreenState);
 		}
 	}
+	if (g_system->hasFeature(OSystem::kFeatureSAFRevokePermsControl)) {
+		if (ConfMan.hasKey("onscreen_saf_revoke_btn", _domain)) {
+			bool showSAFRevokeState =  g_system->getFeatureState(OSystem::kFeatureSAFRevokePermsControl);
+			if (_onscreenSAFRevokeCheckbox != nullptr)
+				_onscreenSAFRevokeCheckbox->setState(showSAFRevokeState);
+		}
+	}
 	if (g_system->hasFeature(OSystem::kFeatureTouchpadMode)) {
 		if (ConfMan.hasKey("touchpad_mouse_mode", _domain)) {
 			bool touchpadState =  g_system->getFeatureState(OSystem::kFeatureTouchpadMode);
@@ -806,6 +814,11 @@ void OptionsDialog::apply() {
 				g_system->setFeatureState(OSystem::kFeatureOnScreenControl, _onscreenCheckbox->getState());
 			}
 		}
+		if (g_system->hasFeature(OSystem::kFeatureSAFRevokePermsControl)) {
+			if (ConfMan.getBool("onscreen_saf_revoke_btn", _domain) != _onscreenSAFRevokeCheckbox->getState()) {
+				g_system->setFeatureState(OSystem::kFeatureSAFRevokePermsControl, _onscreenSAFRevokeCheckbox->getState());
+			}
+		}
 		if (g_system->hasFeature(OSystem::kFeatureTouchpadMode)) {
 			if (ConfMan.getBool("touchpad_mouse_mode", _domain) != _touchpadCheckbox->getState()) {
 				g_system->setFeatureState(OSystem::kFeatureTouchpadMode, _touchpadCheckbox->getState());
@@ -1211,6 +1224,10 @@ void OptionsDialog::addControlControls(GuiObject *boss, const Common::String &pr
 	if (g_system->hasFeature(OSystem::kFeatureOnScreenControl))
 		_onscreenCheckbox = new CheckboxWidget(boss, prefix + "grOnScreenCheckbox", _("Show On-screen control"));
 
+	// Show Overlay button to revoke SAF (storage access) ScummVM permissions (Android specific)
+	if (g_system->hasFeature(OSystem::kFeatureSAFRevokePermsControl))
+		_onscreenSAFRevokeCheckbox = new CheckboxWidget(boss, prefix + "grOnScreenSAFRevokeCheckbox ", _("Show SAF revoke permissions overlay button"));
+
 	// Touchpad Mouse mode
 	if (g_system->hasFeature(OSystem::kFeatureTouchpadMode))
 		_touchpadCheckbox = new CheckboxWidget(boss, prefix + "grTouchpadCheckbox", _("Touchpad mouse mode"));
@@ -1952,6 +1969,7 @@ void GlobalOptionsDialog::build() {
 	//
 	if (g_system->hasFeature(OSystem::kFeatureTouchpadMode) ||
 		g_system->hasFeature(OSystem::kFeatureOnScreenControl) ||
+		g_system->hasFeature(OSystem::kFeatureSAFRevokePermsControl) ||
 		g_system->hasFeature(OSystem::kFeatureSwapMenuAndBackButtons) ||
 		g_system->hasFeature(OSystem::kFeatureKbdMouseSpeed) ||
 		g_system->hasFeature(OSystem::kFeatureJoystickDeadzone)) {
diff --git a/gui/options.h b/gui/options.h
index 156a8c762f..0c670959cf 100644
--- a/gui/options.h
+++ b/gui/options.h
@@ -131,6 +131,7 @@ private:
 
 	CheckboxWidget *_touchpadCheckbox;
 	CheckboxWidget *_onscreenCheckbox;
+	CheckboxWidget *_onscreenSAFRevokeCheckbox;
 	CheckboxWidget *_swapMenuAndBackBtnsCheckbox;
 
 	StaticTextWidget *_kbdMouseSpeedDesc;
diff --git a/gui/themes/default.inc b/gui/themes/default.inc
index 99ca9239d1..0bfa8eacd0 100644
--- a/gui/themes/default.inc
+++ b/gui/themes/default.inc
@@ -1633,6 +1633,9 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
 "<widget name='grTouchpadCheckbox' "
 "type='Checkbox' "
 "/>"
+"<widget name='grOnScreenSAFRevokeCheckbox' "
+"type='Checkbox' "
+"/>"
 "<widget name='grSwapMenuAndBackBtnsCheckbox' "
 "type='Checkbox' "
 "/>"
@@ -3473,6 +3476,9 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
 "<widget name='grTouchpadCheckbox' "
 "type='Checkbox' "
 "/>"
+"<widget name='grOnScreenSAFRevokeCheckbox' "
+"type='Checkbox' "
+"/>"
 "<widget name='grSwapMenuAndBackBtnsCheckbox' "
 "type='Checkbox' "
 "/>"
diff --git a/gui/themes/residualvm.zip b/gui/themes/residualvm.zip
index 1373f44636..eef585ee81 100644
Binary files a/gui/themes/residualvm.zip and b/gui/themes/residualvm.zip differ
diff --git a/gui/themes/residualvm/THEMERC b/gui/themes/residualvm/THEMERC
index cf724c376a..38131f98b0 100644
--- a/gui/themes/residualvm/THEMERC
+++ b/gui/themes/residualvm/THEMERC
@@ -1 +1 @@
-[SCUMMVM_STX0.8.47:ResidualVM Modern Theme:No Author]
+[SCUMMVM_STX0.8.48:ResidualVM Modern Theme:No Author]
diff --git a/gui/themes/residualvm/residualvm_layout.stx b/gui/themes/residualvm/residualvm_layout.stx
index c93237e8c1..6299982b8a 100644
--- a/gui/themes/residualvm/residualvm_layout.stx
+++ b/gui/themes/residualvm/residualvm_layout.stx
@@ -274,6 +274,9 @@
 			<widget name = 'grTouchpadCheckbox'
 					type = 'Checkbox'
 			/>
+			<widget name = 'grOnScreenSAFRevokeCheckbox'
+					type = 'Checkbox'
+			/>
 			<widget name = 'grSwapMenuAndBackBtnsCheckbox'
 					type = 'Checkbox'
 			/>
diff --git a/gui/themes/residualvm/residualvm_layout_lowres.stx b/gui/themes/residualvm/residualvm_layout_lowres.stx
index c41d1f8670..7dc0d4695c 100644
--- a/gui/themes/residualvm/residualvm_layout_lowres.stx
+++ b/gui/themes/residualvm/residualvm_layout_lowres.stx
@@ -256,6 +256,9 @@
 			<widget name = 'grTouchpadCheckbox'
 					type = 'Checkbox'
 			/>
+			<widget name = 'grOnScreenSAFRevokeCheckbox'
+					type = 'Checkbox'
+			/>
 			<widget name = 'grSwapMenuAndBackBtnsCheckbox'
 					type = 'Checkbox'
 			/>
diff --git a/gui/themes/scummclassic.zip b/gui/themes/scummclassic.zip
index 1c042b5757..b47436bf1e 100644
Binary files a/gui/themes/scummclassic.zip and b/gui/themes/scummclassic.zip differ
diff --git a/gui/themes/scummclassic/THEMERC b/gui/themes/scummclassic/THEMERC
index 1168571bad..de9947cf51 100644
--- a/gui/themes/scummclassic/THEMERC
+++ b/gui/themes/scummclassic/THEMERC
@@ -1 +1 @@
-[SCUMMVM_STX0.8.47:ScummVM Classic Theme:No Author]
+[SCUMMVM_STX0.8.48:ScummVM Classic Theme:No Author]
diff --git a/gui/themes/scummmodern.zip b/gui/themes/scummmodern.zip
index 4250abf48f..478accc81d 100644
Binary files a/gui/themes/scummmodern.zip and b/gui/themes/scummmodern.zip differ
diff --git a/gui/themes/scummmodern/THEMERC b/gui/themes/scummmodern/THEMERC
index e9b67c543b..d13e792996 100644
--- a/gui/themes/scummmodern/THEMERC
+++ b/gui/themes/scummmodern/THEMERC
@@ -1 +1 @@
-[SCUMMVM_STX0.8.47:ScummVM Modern Theme:No Author]
+[SCUMMVM_STX0.8.48:ScummVM Modern Theme:No Author]
diff --git a/gui/themes/scummmodern/scummmodern_layout.stx b/gui/themes/scummmodern/scummmodern_layout.stx
index c93237e8c1..6299982b8a 100644
--- a/gui/themes/scummmodern/scummmodern_layout.stx
+++ b/gui/themes/scummmodern/scummmodern_layout.stx
@@ -274,6 +274,9 @@
 			<widget name = 'grTouchpadCheckbox'
 					type = 'Checkbox'
 			/>
+			<widget name = 'grOnScreenSAFRevokeCheckbox'
+					type = 'Checkbox'
+			/>
 			<widget name = 'grSwapMenuAndBackBtnsCheckbox'
 					type = 'Checkbox'
 			/>
diff --git a/gui/themes/scummmodern/scummmodern_layout_lowres.stx b/gui/themes/scummmodern/scummmodern_layout_lowres.stx
index c41d1f8670..7dc0d4695c 100644
--- a/gui/themes/scummmodern/scummmodern_layout_lowres.stx
+++ b/gui/themes/scummmodern/scummmodern_layout_lowres.stx
@@ -256,6 +256,9 @@
 			<widget name = 'grTouchpadCheckbox'
 					type = 'Checkbox'
 			/>
+			<widget name = 'grOnScreenSAFRevokeCheckbox'
+					type = 'Checkbox'
+			/>
 			<widget name = 'grSwapMenuAndBackBtnsCheckbox'
 					type = 'Checkbox'
 			/>
diff --git a/gui/themes/scummremastered.zip b/gui/themes/scummremastered.zip
index 90cc62a86e..361b9a8445 100644
Binary files a/gui/themes/scummremastered.zip and b/gui/themes/scummremastered.zip differ
diff --git a/gui/themes/scummremastered/THEMERC b/gui/themes/scummremastered/THEMERC
index 1272ed64de..bbc4a7857d 100644
--- a/gui/themes/scummremastered/THEMERC
+++ b/gui/themes/scummremastered/THEMERC
@@ -1 +1 @@
-[SCUMMVM_STX0.8.47:ScummVM Modern Theme Remastered:No Author]
+[SCUMMVM_STX0.8.48:ScummVM Modern Theme Remastered:No Author]
diff --git a/gui/themes/scummremastered/remastered_layout.stx b/gui/themes/scummremastered/remastered_layout.stx
index 277dd6f3c3..a93dcf87b0 100644
--- a/gui/themes/scummremastered/remastered_layout.stx
+++ b/gui/themes/scummremastered/remastered_layout.stx
@@ -276,6 +276,9 @@
 			<widget name = 'grTouchpadCheckbox'
 					type = 'Checkbox'
 			/>
+			<widget name = 'grOnScreenSAFRevokeCheckbox'
+					type = 'Checkbox'
+			/>
 			<widget name = 'grSwapMenuAndBackBtnsCheckbox'
 					type = 'Checkbox'
 			/>
diff --git a/gui/themes/scummremastered/remastered_layout_lowres.stx b/gui/themes/scummremastered/remastered_layout_lowres.stx
index 4292b8b7ec..3952929640 100644
--- a/gui/themes/scummremastered/remastered_layout_lowres.stx
+++ b/gui/themes/scummremastered/remastered_layout_lowres.stx
@@ -258,6 +258,9 @@
 			<widget name = 'grTouchpadCheckbox'
 					type = 'Checkbox'
 			/>
+			<widget name = 'grOnScreenSAFRevokeCheckbox'
+					type = 'Checkbox'
+			/>
 			<widget name = 'grSwapMenuAndBackBtnsCheckbox'
 					type = 'Checkbox'
 			/>




More information about the Scummvm-git-logs mailing list