androidurimediastoreandroid-fileprovider

Android: FileProvider.getUriForFile reading MediaStore-generated file?


TL;DR

I wanna get(read/generate) file Uri -- with path like below -- by FileProvider, but don't know how:

val file = File("/external/file/9359") // how do I get its Uri then?

All I know was FileProvider.getUriForFile method, but it throws exception.

What I was doing

I was trying to mock a download progress -- create a file in Download folder, and then pass it to ShareSheet to let user do whatever it want to do.

What I have done:

  1. Create file in Download by using MediaStore and ContentResolver.
  2. Have a ShareSheet util function.
  3. Registered FileProvider and filepath.xml.

In my case, I want to share the file via ShareSheet function, which requires

but the usual file.toUri() will throw exception above SDK 29. Hence I change it into FileProvider.getUriForFile() as Google-official recommended.

The Direct Problem Code

val fileUri: Uri = FileProvider.getUriForFile(context, "my.provider", file)

Will throw exception:

java.lang.IllegalArgumentException: Failed to find configured root that contains /external/file/9359

My File Creation Code

val fileUri: Uri? = ContentValues().apply {
    put(MediaStore.MediaColumns.DISPLAY_NAME, "test.txt")
    put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // create file in download directory.
        put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
    }
}.let { values ->
    context.contentResolver.insert(MediaStore.Files.getContentUri("external"), values)
}
if (fileUri == null) return

context.contentResolver.openOutputStream(fileUri)?.use { fileOut ->
    fileOut.write("Hello, World!".toByteArray())
}

val file = File(fileUri.path!!) // File("/external/file/9359")

I can assure the code is correct because I can see the correct file in Download folder.

My FileProvider Setup

I have registered provider in AndroidManifest.xml:

<application>
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="my.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepath" />
    </provider>
</application>

with filepath.xml below:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="external"
        path="." />
</paths>

I've also tried so many path variants, one by one:

<external-files-path name="download" path="Download/" />
<external-files-path name="download" path="." />
<external-path name="download" path="Download/" />
<external-path name="download" path="." />
<external-path name="external" path="." />
<external-files-path name="external_files" path="." />

I also even registered external storage reading permissions:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

which part did I do wrong?

Edit 1

After posting, I thought the File initialization might be the problem:

File(fileUri.path!!)

So I changed it to

File(fileUri.toString())

but still not working.

Their content difference is below btw:

fileUri.path       -> /external/file/9359
(file.path)        -> /external/file/9359
error              -> /external/file/9359

fileUri.toString() -> content://media/external/file/9359
(file.path)        -> content:/media/external/file/9359
error              -> /content:/media/external/file/9359

Edit 2

What I originally wanted to achieve is sending binary data to other app. As Official-documented, it seems only accept nothing but file Uri.

I'd be appreciate if there's any other way to achieve this, like share the File directly, etc.

But what I'm wondering now is simple -- How do I make FileProvider available to get/read/generate file Uri like /external/file/9359 or so on? This might comes in help not only this case, and seems like a more general/basic knowledge to me.


Solution

  • Content uris does not have file-path or scheme. You should create an input stream and by using this inputstream create a temporary file. You can extract a file-uri or path from this file. Here is a function of mine to create a temporary file from content-uri:

     fun createFileFromContentUri(fileUri : Uri) : File{
    
        var fileName : String = ""
    
        fileUri.let { returnUri ->
            requireActivity().contentResolver.query(returnUri,null,null,null)
        }?.use { cursor ->
            val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
            cursor.moveToFirst()
            fileName = cursor.getString(nameIndex)
        }
        
        //  For extract file mimeType
        val fileType: String? = fileUri.let { returnUri ->
            requireActivity().contentResolver.getType(returnUri)
        }
    
        val iStream : InputStream = requireActivity().contentResolver.openInputStream(fileUri)!!
        val outputDir : File = context?.cacheDir!!
        val outputFile : File = File(outputDir,fileName)
        copyStreamToFile(iStream, outputFile)
        iStream.close()
        return  outputFile
    }
    
    fun copyStreamToFile(inputStream: InputStream, outputFile: File) {
        inputStream.use { input ->
            val outputStream = FileOutputStream(outputFile)
            outputStream.use { output ->
                val buffer = ByteArray(4 * 1024) // buffer size
                while (true) {
                    val byteCount = input.read(buffer)
                    if (byteCount < 0) break
                    output.write(buffer, 0, byteCount)
                }
                output.flush()
            }
        }
       }
    

    --Edit--

    Content Uri has path but not file path. For example;

    content Uri path: content://media/external/audio/media/710 ,

    file uri path : file:///sdcard/media/audio/ringtones/nevergonnagiveyouup.mp3

    By using the function above you can create a temporary file and you can extract uri and path like this:

    val tempFile: File = createFileFromContentUri(contentUri)  
    val filePath = tempFile.path  
    val fileUri = tempFile.getUri() 
    

    After using tempFile delete it to prevent memory issues(it will be deleted because it is written in cache ):

    tempFile.delete()
    

    There is no need to edit content uri. Adding scheme to content uri probably not gonna work