1

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

  • The Uri of File

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.

Samuel T. Chou
  • 349
  • 2
  • 23

4 Answers4

1

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

Alperen Acikgoz
  • 89
  • 1
  • 2
  • 7
  • "Context uris does not have path or scheme." that might be the part I miss! Can you elaborate? Like the reference, or is there any chance that we can add path/scheme back to that Uri? (ex. the one stated in the question) – Samuel T. Chou Feb 23 '22 at 02:33
  • @SamuelT.Chou You can check [this] (https://developer.android.com/reference/android/content/ContentUris) documentation. 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_ – Alperen Acikgoz Feb 23 '22 at 06:21
  • 1
    @SamuelT.Chou "is there any chance that we can add path/scheme back to that Uri?" -> by using the function above you can create a temporary file and yo can extract uri and path like this: `val tempFile: File = createFileFromContentUri(contentUri)` - `val filePath = tempFile.path` and `val fileUri = tempFile.getUri()`. So you don't have to edit content uri. Adding scheme to content uri probably not gonna work – Alperen Acikgoz Feb 23 '22 at 06:29
  • 1
    Well IMO, re-creating a file temporally and use that instead seems a little bit overkill (since we already have the file). But the info you provided is very helpful to understand the whole thing. I suggest you to edit the comments into your answer, then I'll consider accept it. – Samuel T. Chou Feb 23 '22 at 06:56
  • 1
    "re-creating a file temporally and use that instead seems a little bit overkill" -> same idea. The same question asked [here](https://commonsware.com/community/t/get-file-from-uri/624) . And here is a useful [link](https://commonsware.com/blog/2016/03/16/how-publish-files-via-content-uri.html) describes file-uri relations quite well – Alperen Acikgoz Feb 23 '22 at 07:40
0

bro try this one may it works for you , change your path.xml file to this ;

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

or check this one also if behind solution not working

https://stackoverflow.com/a/42516202/7905787

Adnan Bashir
  • 280
  • 2
  • 7
  • Yeah I saw this before, tried it, and still not working. The answer focus on the file created by `Context` -provided directory; but in my situation, I'm using `MediaStore`. – Samuel T. Chou Feb 15 '22 at 07:13
  • If you can help me change save-style from `MediaStore` into `Context`-provided directory, though might not answering the question, but still helps me. I read and tried a lot, it seems that `Context` ones could not really create a file in `Download` folder...? – Samuel T. Chou Feb 15 '22 at 07:16
0

content:/media/external/file/9370

You can just read that mediastore uri by opening a stream for it:

InputStream is = getContentResolver().openInpurStream(uri);

Then read from the stream.

If you wanna share the file then use this uri as is.

No need to use FileProvider if you have already a nice uri.

blackapps
  • 6,227
  • 2
  • 6
  • 19
  • I'm doing the "share-file" action as official document written, which seems only accepts uri. https://developer.android.com/training/sharing/send#send-binary-content Then I should only look for Uri. How could I "just read the uri from the stream"? (which is opened from uri...?) – Samuel T. Chou Feb 15 '22 at 07:25
  • Can you edit your post and tell that you wanna share a file? As now we can only read rhat you wanna read a file. Confusing as you can see. – blackapps Feb 15 '22 at 09:10
  • I want to know how can I read `Uri` of `File` from `FileProvider` -- which I stated in title and First section. If you answer the part of how I can share file not by `Uri`, I would be appreciate, but not close the question (or mark as accepted answer). That path-reading is a puzzle I'd like to solve now. – Samuel T. Chou Feb 15 '22 at 10:39
  • Oh, I understand which part confuses you now. I made it clear, hope the description is clear enough now. – Samuel T. Chou Feb 15 '22 at 10:44
  • ??? I see nothing changed. You still dont start your post with telling that you wanna share a file. Also the title of your post does not say such. – blackapps Feb 15 '22 at 10:47
  • File-sharing is not the point I want to deal with (because the error is not there). I'm more like looking for answer of how to get `Uri` of a specific file, or the correct combination of `MediaStore` and `FileProvider`. – Samuel T. Chou Feb 15 '22 at 10:57
  • I totally have no idea what you want. You wrote an unreadable post. Please rewrite it totally. And remember: it makes no sense to use FileProvider in combination with a MediaStore uri. You have been said that before. – blackapps Feb 15 '22 at 11:00
  • Well, I _didn't_ say that `FileProvider` should not be used with a `MediaStore` uri. Maybe you can elaborate that part? That could be an answer. – Samuel T. Chou Feb 16 '22 at 03:49
0
public  void image_download(Context context, String uRl, String bookNmae) {

        File direct = new File(Environment.getExternalStorageDirectory()
                + "/Islam Video App/books");

        if (!direct.exists()) {
            direct.mkdirs();
        }

        DownloadManager mgr = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);

        Uri downloadUri = Uri.parse(uRl);
        DownloadManager.Request request = new DownloadManager.Request(
                downloadUri);

        request.setAllowedNetworkTypes(
                DownloadManager.Request.NETWORK_WIFI
                        | DownloadManager.Request.NETWORK_MOBILE)
                .setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN)
                .setAllowedOverRoaming(false).setTitle(bookNmae)
                .setDestinationInExternalPublicDir("/Islam Video App/books", bookNmae + ".pdf");

        mgr.enqueue(request);

    }
Adnan Bashir
  • 280
  • 2
  • 7
  • Sorry, but `DownloadManager` doesn't match my need. My API uses `POST` method with request body, which is not allowed by `DownloadManager`. – Samuel T. Chou Feb 15 '22 at 07:28