fixed SD card access grant on Android Lollipop

This commit is contained in:
Thibault Deckers 2023-01-22 21:50:37 +01:00
parent 788241fab3
commit fda5865e26
3 changed files with 46 additions and 9 deletions

View file

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased]
### Fixed
- SD card access grant on Android Lollipop
## <a id="v1.7.10"></a>[v1.7.10] - 2023-01-18
### Added

View file

@ -41,7 +41,7 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
"canGrantDirectoryAccess" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP),
"canPinShortcut" to ShortcutManagerCompat.isRequestPinShortcutSupported(context),
"canPrint" to (sdkInt >= Build.VERSION_CODES.KITKAT),
"canRenderFlagEmojis" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP),
"canRenderFlagEmojis" to (sdkInt >= Build.VERSION_CODES.M),
"canRequestManageMedia" to (sdkInt >= Build.VERSION_CODES.S),
"canSetLockScreenWallpaper" to (sdkInt >= Build.VERSION_CODES.N),
"hasGeocoder" to Geocoder.isPresent(),

View file

@ -39,6 +39,7 @@ object StorageUtils {
private const val TREE_URI_ROOT = "content://$EXTERNAL_STORAGE_PROVIDER_AUTHORITY/tree/"
private val UUID_PATTERN = Regex("[A-Fa-f\\d-]+")
private val TREE_URI_PATH_PATTERN = Pattern.compile("(.*?):(.*)")
const val TRASH_PATH_PLACEHOLDER = "#trash"
@ -259,6 +260,7 @@ object StorageUtils {
// e.g.
// /storage/emulated/0/ -> primary
// /storage/10F9-3F13/Pictures/ -> 10F9-3F13
// /storage/extSdCard/ -> 1234-5678 [Android 5.1.1, Samsung Galaxy Core Prime]
private fun getVolumeUuidForDocumentUri(context: Context, anyPath: String): String? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val sm = context.getSystemService(Context.STORAGE_SERVICE) as? StorageManager
@ -278,7 +280,22 @@ object StorageUtils {
return EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID
}
volumePath.split(File.separator).lastOrNull { it.isNotEmpty() }?.let { uuid ->
return uuid.uppercase(Locale.ROOT)
if (uuid.matches(UUID_PATTERN)) {
return uuid.uppercase(Locale.ROOT)
}
}
// fallback when UUID does not appear in the SD card volume path
context.contentResolver.persistedUriPermissions.firstOrNull { uriPermission ->
convertTreeDocumentUriToDirPath(context, uriPermission.uri)?.let {
getVolumePath(context, it)?.let { grantedVolumePath ->
grantedVolumePath == volumePath
}
} ?: false
}?.let { uriPermission ->
splitTreeDocumentUri(uriPermission.uri)?.let { (uuid, _) ->
return uuid
}
}
}
@ -289,6 +306,7 @@ object StorageUtils {
// e.g.
// primary -> /storage/emulated/0/
// 10F9-3F13 -> /storage/10F9-3F13/
// 1234-5678 -> /storage/extSdCard/ [Android 5.1.1, Samsung Galaxy Core Prime]
private fun getVolumePathFromTreeDocumentUriUuid(context: Context, uuid: String): String? {
if (uuid == EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID) {
return getPrimaryVolumePath(context)
@ -318,6 +336,10 @@ object StorageUtils {
}
}
// fallback when UUID does not appear in the SD card volume path
val primaryVolumePath = getPrimaryVolumePath(context)
getVolumePaths(context).firstOrNull { it != primaryVolumePath }?.let { return it }
Log.e(LOG_TAG, "failed to find volume path for UUID=$uuid")
return null
}
@ -350,9 +372,9 @@ object StorageUtils {
}
// e.g.
// content://com.android.externalstorage.documents/tree/primary%3A -> /storage/emulated/0/
// content://com.android.externalstorage.documents/tree/10F9-3F13%3APictures -> /storage/10F9-3F13/Pictures/
fun convertTreeDocumentUriToDirPath(context: Context, treeDocumentUri: Uri): String? {
// content://com.android.externalstorage.documents/tree/primary%3A -> ("primary", "")
// content://com.android.externalstorage.documents/tree/10F9-3F13%3APictures -> ("10F9-3F13", "Pictures")
private fun splitTreeDocumentUri(treeDocumentUri: Uri): Pair<String, String>? {
val treeDocumentUriString = treeDocumentUri.toString()
if (treeDocumentUriString.length <= TREE_URI_ROOT.length) return null
val encoded = treeDocumentUriString.substring(TREE_URI_ROOT.length)
@ -362,13 +384,24 @@ object StorageUtils {
val uuid = group(1)
val relativePath = group(2)
if (uuid != null && relativePath != null) {
val volumePath = getVolumePathFromTreeDocumentUriUuid(context, uuid)
if (volumePath != null) {
return ensureTrailingSeparator(volumePath + relativePath)
}
return Pair(uuid, relativePath)
}
}
}
Log.e(LOG_TAG, "failed to split treeDocumentUri=$treeDocumentUri to UUID and relative path")
return null
}
// e.g.
// content://com.android.externalstorage.documents/tree/primary%3A -> /storage/emulated/0/
// content://com.android.externalstorage.documents/tree/10F9-3F13%3APictures -> /storage/10F9-3F13/Pictures/
fun convertTreeDocumentUriToDirPath(context: Context, treeDocumentUri: Uri): String? {
splitTreeDocumentUri(treeDocumentUri)?.let { (uuid, relativePath) ->
val volumePath = getVolumePathFromTreeDocumentUriUuid(context, uuid)
if (volumePath != null) {
return ensureTrailingSeparator(volumePath + relativePath)
}
}
Log.e(LOG_TAG, "failed to convert treeDocumentUri=$treeDocumentUri to path")
return null
}