Does anyone else have experience with this? Our app prompts users to select the root of a USB drive in order to grant access to it. But for a small minority of users, they get an error like this that prevents them from making any selections in the SAF picker:

enter image description here

This seems like an Android system bug to me. I've considered implementing All Files Access as a workaround for these users, but that feels like a nuclear option.

Here is the code I use for opening the SAF picker:

    private fun promptSAFPermissions() {
        val storageManager = requireActivity().getSystemService(Context.STORAGE_SERVICE) as StorageManager
        val storageVolumes = storageManager.recentStorageVolumes

        // The storageVolumes list returns both USB drives and SD cards, so here we try to distinguish
        // between these. We use the first one listed, since it's more likely to be a USB drive.
        val usbDriveVolume = storageVolumes.firstOrNull {
            !it.isPrimary && it.isRemovable && it.state == Environment.MEDIA_MOUNTED && !isRemovableVolumeAnSdCard(it)
        }

        val intent = when {
            usbDriveVolume != null && usbDriveVolume.state == Environment.MEDIA_MOUNTED -> {
                // If a matching volume was found and it's mounted, open SAF picker directly to it.
                usbDriveVolume.createOpenDocumentTreeIntent()
            }
            else -> {
                // If not matching volume was found, open SAF picker at root and let user try to choose it manually.
                Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
            }
        }

        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)

        requireActivity().startActivityForResult(intent, REQUEST_CODE_SAF_PERMISSION)
    }
// Try to determine if volume is an SD card.
// Return true if the given StorageVolume is an SD card. First it checks whether "sdcard" is in the volume description.
// Then it reads the proc/mounts file, which contains mount info for all drives on device.
// A USB drive will show on a line like: /dev/block/vold/public:8,1 on /mnt/media_rw/68C9-D020 type vfat (rw,dirsync,nosuid,nodev,noexec,noatime,uid=1023,gid=1023,fmask=0007,dmask=0007,allow_utime=0020,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro)
// An SD card will show on a line like:  /dev/block/vold/public:179,65 on /mnt/media_rw/084E-056D type vfat (rw,dirsync,nosuid,nodev,noexec,noatime,uid=1023,gid=1023,fmask=0007,dmask=0007,allow_utime=0020,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro)
// The /dev/block/vold/public:8 indicates a USB drive at 68C9-D020, while /dev/block/vold/public:179 indicates an SD card at 084E-056D.
fun isRemovableVolumeAnSdCard(volume: StorageVolume): Boolean {
    // If we have already determined whether the volume is an SD card, the value was stored in uuidIsAnSdCardMap. So just return the value we already found.
    PKDrive.instance.uuidIsAnSdCardMap[volume.uuid]?.let { uuidIsAnSdCard ->
        return uuidIsAnSdCard
    }

    val description = volume.getDescription(App.appContext).trim { it <= ' ' }.replace(" ", "").lowercase(Locale.ROOT)
    if (description.contains("sdcard")) return true

    if (volume.uuid == null) return false

    var bufferedReader: BufferedReader? = null
    var inputStream: InputStream? = null
    var inputStreamReader: InputStreamReader? = null
    var sdCardFound = false
    try {
        val runtime = Runtime.getRuntime()
        val process = runtime.exec("mount")
        inputStream = process.inputStream
        inputStreamReader = InputStreamReader(inputStream)

        bufferedReader = BufferedReader(inputStreamReader)
        bufferedReader.forEachLine { line ->
            if (line.contains(volume.uuid!!, true) && line.contains("fat") && line.contains("media_rw") && line.contains("/dev/block/vold/public:179")) {
                sdCardFound = true
                return@forEachLine
            }
        }
    } catch (ignored: Exception) {
    } finally {
        try {
            bufferedReader?.close()
            inputStream?.close()
            inputStreamReader?.close()
        } catch (ignored: Exception) {
        }
        // Add result of this method to uuidIsAnSdCardMap, so we don't have to repeat this operation for the same StorageVolume in the future.
        PKDrive.instance.uuidIsAnSdCardMap[volume.uuid!!] = sdCardFound
    }
    return sdCardFound
}
0

There are 0 best solutions below