diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 66f30eaf6..3d853ef60 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -14,10 +14,6 @@
https://developer.android.com/preview/privacy/storage#media-file-access
- raw path access:
https://developer.android.com/preview/privacy/storage#media-files-raw-paths
-
- Android R issues:
- - users cannot grant directory access to the root Downloads directory,
- - users cannot grant directory access to the root directory of each reliable SD card volume
-->
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt
index 6046c7508..d35b00134 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt
@@ -325,7 +325,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
if (xmpMeta.doesPropertyExist(XMP.DC_SCHEMA_NS, XMP.SUBJECT_PROP_NAME)) {
val count = xmpMeta.countArrayItems(XMP.DC_SCHEMA_NS, XMP.SUBJECT_PROP_NAME)
val values = (1 until count + 1).map { xmpMeta.getArrayItem(XMP.DC_SCHEMA_NS, XMP.SUBJECT_PROP_NAME, it).value }
- metadataMap[KEY_XMP_SUBJECTS] = values.joinToString(separator = XMP_SUBJECTS_SEPARATOR)
+ metadataMap[KEY_XMP_SUBJECTS] = values.joinToString(XMP_SUBJECTS_SEPARATOR)
}
xmpMeta.getSafeLocalizedText(XMP.DC_SCHEMA_NS, XMP.TITLE_PROP_NAME, acceptBlank = false) { metadataMap[KEY_XMP_TITLE_DESCRIPTION] = it }
if (!metadataMap.containsKey(KEY_XMP_TITLE_DESCRIPTION)) {
@@ -350,7 +350,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
// XMP fallback to IPTC
if (!metadataMap.containsKey(KEY_XMP_SUBJECTS)) {
for (dir in metadata.getDirectoriesOfType(IptcDirectory::class.java)) {
- dir.keywords?.let { metadataMap[KEY_XMP_SUBJECTS] = it.joinToString(separator = XMP_SUBJECTS_SEPARATOR) }
+ dir.keywords?.let { metadataMap[KEY_XMP_SUBJECTS] = it.joinToString(XMP_SUBJECTS_SEPARATOR) }
}
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt
index 26acc1bf6..9199bb43c 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/StorageHandler.kt
@@ -26,6 +26,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
"getFreeSpace" -> safe(call, result, ::getFreeSpace)
"getGrantedDirectories" -> safe(call, result, ::getGrantedDirectories)
"getInaccessibleDirectories" -> safe(call, result, ::getInaccessibleDirectories)
+ "getRestrictedDirectories" -> safe(call, result, ::getRestrictedDirectories)
"revokeDirectoryAccess" -> safe(call, result, ::revokeDirectoryAccess)
"scanFile" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::scanFile) }
else -> result.notImplemented()
@@ -104,8 +105,11 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
return
}
- val dirs = PermissionManager.getInaccessibleDirectories(context, dirPaths)
- result.success(dirs)
+ result.success(PermissionManager.getInaccessibleDirectories(context, dirPaths))
+ }
+
+ private fun getRestrictedDirectories(@Suppress("UNUSED_PARAMETER") call: MethodCall, result: MethodChannel.Result) {
+ result.success(PermissionManager.getRestrictedDirectories(context))
}
private fun revokeDirectoryAccess(call: MethodCall, result: MethodChannel.Result) {
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt
index 49d1b005b..e703392e5 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/PermissionManager.kt
@@ -5,12 +5,14 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
+import android.os.Environment
import android.os.storage.StorageManager
import android.util.Log
import deckers.thibault.aves.utils.StorageUtils.PathSegments
import java.io.File
import java.util.*
import java.util.concurrent.ConcurrentHashMap
+import kotlin.collections.ArrayList
object PermissionManager {
private val LOG_TAG = LogUtils.createTag(PermissionManager::class.java)
@@ -66,9 +68,20 @@ object PermissionManager {
val dirSet = dirsPerVolume[volumePath] ?: HashSet()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// request primary directory on volume from Android R
- segments.relativeDir?.apply {
- val primaryDir = split(File.separator).firstOrNull { it.isNotEmpty() }
- primaryDir?.let { dirSet.add(it) }
+ val relativeDir = segments.relativeDir
+ if (relativeDir != null) {
+ val dirSegments = relativeDir.split(File.separator).takeWhile { it.isNotEmpty() }
+ val primaryDir = dirSegments.firstOrNull()
+ if (primaryDir == Environment.DIRECTORY_DOWNLOADS && dirSegments.size > 1) {
+ // request secondary directory (if any) for restricted primary directory
+ dirSet.add(dirSegments.take(2).joinToString(File.separator))
+ } else {
+ primaryDir?.let { dirSet.add(it) }
+ }
+ } else {
+ // the requested path is the volume root itself
+ // which cannot be granted, due to Android R restrictions
+ dirSet.add("")
}
} else {
// request volume root until Android Q
@@ -92,6 +105,30 @@ object PermissionManager {
}
}
+ fun getRestrictedDirectories(context: Context): List