diff --git a/platforms/android/app/src/main/AndroidManifest.xml b/platforms/android/app/src/main/AndroidManifest.xml
index f5a0365856..cd687963e1 100644
--- a/platforms/android/app/src/main/AndroidManifest.xml
+++ b/platforms/android/app/src/main/AndroidManifest.xml
@@ -11,6 +11,7 @@
+
{
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION |
@@ -185,9 +204,71 @@ public class DuskActivity extends SDLActivity {
finishFolderDialog(Activity.RESULT_CANCELED, null);
}
});
+ }
+
+ private boolean requiresManageStoragePermission() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
+ }
+
+ private boolean hasManageStoragePermission() {
+ return !requiresManageStoragePermission() || Environment.isExternalStorageManager();
+ }
+
+ private boolean requestManageStoragePermission() {
+ if (!requiresManageStoragePermission()) {
+ return true;
+ }
+
+ awaitingManageStoragePermission = true;
+ runOnUiThread(() -> {
+ if (tryStartManageStorageIntent(
+ new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
+ .setData(Uri.parse("package:" + getPackageName()))) ||
+ tryStartManageStorageIntent(
+ new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)))
+ {
+ return;
+ }
+
+ finishFolderDialogWithError("Unable to request Android file access permission");
+ });
return true;
}
+ private boolean tryStartManageStorageIntent(Intent intent) {
+ try {
+ startActivityForResult(intent, MANAGE_STORAGE_REQUEST_CODE);
+ return true;
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Unable to open all-files access settings.", e);
+ return false;
+ }
+ }
+
+ private void resumeFolderDialogAfterPermissionGrant() {
+ awaitingManageStoragePermission = false;
+ if (folderDialogUserdata == 0) {
+ return;
+ }
+
+ if (hasManageStoragePermission()) {
+ openFolderDialog();
+ return;
+ }
+
+ finishFolderDialogWithError(
+ "Allow \"All files access\" for Dusklight before choosing a custom data folder");
+ }
+
+ private void finishFolderDialogWithError(String error) {
+ long userdata = folderDialogUserdata;
+ folderDialogUserdata = 0;
+ awaitingManageStoragePermission = false;
+ if (userdata != 0) {
+ nativeFolderDialogResult(userdata, null, error);
+ }
+ }
+
private void finishFolderDialog(int resultCode, Intent data) {
long userdata = folderDialogUserdata;
folderDialogUserdata = 0;
diff --git a/src/dusk/data.cpp b/src/dusk/data.cpp
index ce61965947..dc666dd236 100644
--- a/src/dusk/data.cpp
+++ b/src/dusk/data.cpp
@@ -525,7 +525,14 @@ bool validate_writable_data_path(const std::filesystem::path& path, std::string*
try {
io::FileStream::WriteAllText(probePath, "dusk");
} catch (const std::exception& e) {
+#if defined(__ANDROID__)
+ set_error(errorOut,
+ fmt::format("{} could not write to the selected folder. On Android, allow "
+ "\"All files access\" for Dusklight and try again.",
+ AppName));
+#else
set_error(errorOut, fmt::format("{} could not write to the selected folder.", AppName));
+#endif
Log.warn("Failed write probe for custom data folder '{}': {}", io::fs_path_to_string(path),
e.what());
return false;
diff --git a/src/dusk/data.hpp b/src/dusk/data.hpp
index c3ce495b46..bf20035a2d 100644
--- a/src/dusk/data.hpp
+++ b/src/dusk/data.hpp
@@ -15,7 +15,7 @@
#define DUSK_CAN_OPEN_DATA_FOLDER 0
#endif
-#if (defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || defined(__ANDROID__)
+#if (defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST)
#define DUSK_CAN_CHANGE_DATA_FOLDER 0
#else
#define DUSK_CAN_CHANGE_DATA_FOLDER 1