fixed SD card access grant on Android Lollipop
This commit is contained in:
parent
788241fab3
commit
fda5865e26
3 changed files with 46 additions and 9 deletions
|
@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## <a id="unreleased"></a>[Unreleased]
|
## <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
|
## <a id="v1.7.10"></a>[v1.7.10] - 2023-01-18
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -41,7 +41,7 @@ class DeviceHandler(private val context: Context) : MethodCallHandler {
|
||||||
"canGrantDirectoryAccess" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP),
|
"canGrantDirectoryAccess" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP),
|
||||||
"canPinShortcut" to ShortcutManagerCompat.isRequestPinShortcutSupported(context),
|
"canPinShortcut" to ShortcutManagerCompat.isRequestPinShortcutSupported(context),
|
||||||
"canPrint" to (sdkInt >= Build.VERSION_CODES.KITKAT),
|
"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),
|
"canRequestManageMedia" to (sdkInt >= Build.VERSION_CODES.S),
|
||||||
"canSetLockScreenWallpaper" to (sdkInt >= Build.VERSION_CODES.N),
|
"canSetLockScreenWallpaper" to (sdkInt >= Build.VERSION_CODES.N),
|
||||||
"hasGeocoder" to Geocoder.isPresent(),
|
"hasGeocoder" to Geocoder.isPresent(),
|
||||||
|
|
|
@ -39,6 +39,7 @@ object StorageUtils {
|
||||||
|
|
||||||
private const val TREE_URI_ROOT = "content://$EXTERNAL_STORAGE_PROVIDER_AUTHORITY/tree/"
|
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("(.*?):(.*)")
|
private val TREE_URI_PATH_PATTERN = Pattern.compile("(.*?):(.*)")
|
||||||
|
|
||||||
const val TRASH_PATH_PLACEHOLDER = "#trash"
|
const val TRASH_PATH_PLACEHOLDER = "#trash"
|
||||||
|
@ -259,6 +260,7 @@ object StorageUtils {
|
||||||
// e.g.
|
// e.g.
|
||||||
// /storage/emulated/0/ -> primary
|
// /storage/emulated/0/ -> primary
|
||||||
// /storage/10F9-3F13/Pictures/ -> 10F9-3F13
|
// /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? {
|
private fun getVolumeUuidForDocumentUri(context: Context, anyPath: String): String? {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
val sm = context.getSystemService(Context.STORAGE_SERVICE) as? StorageManager
|
val sm = context.getSystemService(Context.STORAGE_SERVICE) as? StorageManager
|
||||||
|
@ -278,10 +280,25 @@ object StorageUtils {
|
||||||
return EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID
|
return EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID
|
||||||
}
|
}
|
||||||
volumePath.split(File.separator).lastOrNull { it.isNotEmpty() }?.let { uuid ->
|
volumePath.split(File.separator).lastOrNull { it.isNotEmpty() }?.let { uuid ->
|
||||||
|
if (uuid.matches(UUID_PATTERN)) {
|
||||||
return uuid.uppercase(Locale.ROOT)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Log.e(LOG_TAG, "failed to find volume UUID for anyPath=$anyPath")
|
Log.e(LOG_TAG, "failed to find volume UUID for anyPath=$anyPath")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -289,6 +306,7 @@ object StorageUtils {
|
||||||
// e.g.
|
// e.g.
|
||||||
// primary -> /storage/emulated/0/
|
// primary -> /storage/emulated/0/
|
||||||
// 10F9-3F13 -> /storage/10F9-3F13/
|
// 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? {
|
private fun getVolumePathFromTreeDocumentUriUuid(context: Context, uuid: String): String? {
|
||||||
if (uuid == EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID) {
|
if (uuid == EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID) {
|
||||||
return getPrimaryVolumePath(context)
|
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")
|
Log.e(LOG_TAG, "failed to find volume path for UUID=$uuid")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -350,9 +372,9 @@ object StorageUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
// e.g.
|
// e.g.
|
||||||
// content://com.android.externalstorage.documents/tree/primary%3A -> /storage/emulated/0/
|
// content://com.android.externalstorage.documents/tree/primary%3A -> ("primary", "")
|
||||||
// content://com.android.externalstorage.documents/tree/10F9-3F13%3APictures -> /storage/10F9-3F13/Pictures/
|
// content://com.android.externalstorage.documents/tree/10F9-3F13%3APictures -> ("10F9-3F13", "Pictures")
|
||||||
fun convertTreeDocumentUriToDirPath(context: Context, treeDocumentUri: Uri): String? {
|
private fun splitTreeDocumentUri(treeDocumentUri: Uri): Pair<String, String>? {
|
||||||
val treeDocumentUriString = treeDocumentUri.toString()
|
val treeDocumentUriString = treeDocumentUri.toString()
|
||||||
if (treeDocumentUriString.length <= TREE_URI_ROOT.length) return null
|
if (treeDocumentUriString.length <= TREE_URI_ROOT.length) return null
|
||||||
val encoded = treeDocumentUriString.substring(TREE_URI_ROOT.length)
|
val encoded = treeDocumentUriString.substring(TREE_URI_ROOT.length)
|
||||||
|
@ -362,13 +384,24 @@ object StorageUtils {
|
||||||
val uuid = group(1)
|
val uuid = group(1)
|
||||||
val relativePath = group(2)
|
val relativePath = group(2)
|
||||||
if (uuid != null && relativePath != null) {
|
if (uuid != null && relativePath != null) {
|
||||||
|
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)
|
val volumePath = getVolumePathFromTreeDocumentUriUuid(context, uuid)
|
||||||
if (volumePath != null) {
|
if (volumePath != null) {
|
||||||
return ensureTrailingSeparator(volumePath + relativePath)
|
return ensureTrailingSeparator(volumePath + relativePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.e(LOG_TAG, "failed to convert treeDocumentUri=$treeDocumentUri to path")
|
Log.e(LOG_TAG, "failed to convert treeDocumentUri=$treeDocumentUri to path")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue