8

How can I serve files from the SECONDARY external storage using the FileProvider?

The current implementation of the FileProvider handles only the first directory returned by ContextCompat.getExternalFilesDirs

...    
} else if (TAG_EXTERNAL_FILES.equals(tag)) {
   File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null);
   if (externalFilesDirs.length > 0) {
       target = externalFilesDirs[0];
   }
}
...

It seems, that there is no way to define a <path> entry for the FileProvider, that matches the secondary external storage path...

artkoenig
  • 7,117
  • 2
  • 40
  • 61
  • What do you mean with secondary external storage? – greenapps Oct 29 '16 at 10:45
  • Directories returned by `ContextCompat.getExternalFilesDirs` with the array index > 0. On the most devices it will probably be the removable sd card. – artkoenig Oct 29 '16 at 10:50
  • AFAIK, `FileProvider` does not support this. You could rig something up with [my `StreamProvider`](https://github.com/commonsguy/cwac-provider), though there is no support for this "out of the box". I have added that to my to-do list for `StreamProvider`, as you make a good point. Particularly since no app permissions are involved for these locations, they ought to be serve-able, at least when they exist. What may get tricky is correctly handling the case where removable storage is not available, but you asked for it to be served up. – CommonsWare Oct 29 '16 at 11:32

5 Answers5

7

FileProvider not support secondary storage because of the code below:

Code from support:support-core-utils:26.1.0 FileProvider

            } else if (TAG_EXTERNAL_FILES.equals(tag)) {
                File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null);
                if (externalFilesDirs.length > 0) {
                    target = externalFilesDirs[0];// Code here, That's why!!!
                }
            } else if (TAG_EXTERNAL_CACHE.equals(tag)) {

However, there is a special TAG in FileProvider : root-path which is not covered in official reference.

            if (TAG_ROOT_PATH.equals(tag)) {
                target = DEVICE_ROOT;// DEVICE_ROOT = new File("/");
            } else if (TAG_FILES_PATH.equals(tag)) {

So, root-path matches all the path.

Just type this code in your FileProvider xml, then FileProvider can handle File in secondary storage.

<root-path name="root" path="." />

Be aware, it may leak your directory structure.

Shaw
  • 1,445
  • 16
  • 21
  • What do you mean by "Be aware, it may leak your directory structure."? Can you elaborate a bit? – Cyber Avater Feb 16 '23 at 13:27
  • 1
    @CyberAvater if you share your file by root-path TAG, the third party app who receives your shared file can see the whole path of your file in the content uri. If you share your user's avatar for example in /data/data/{pkg}/files/{userId}/avatar.jpg, they may know your user's userId because they saw the userId in your filepath – Shaw Feb 17 '23 at 07:11
5

And the answer is... FileProvider does not support that. With Android 7 this is even more an issue, due to deprecation of the file:// Uri scheme.

I issued a bug report.

artkoenig
  • 7,117
  • 2
  • 40
  • 61
  • Thanks for the bug report and the link, another developer provides a working subclass of FileProvider there. – Tim Autin Nov 17 '19 at 23:14
3

To handle files located on external sdcards, I changed my provider_paths.xml to

<paths>
    <external-path path="." name="external_files" />
    <root-path path="." name="sdcard1" />

</paths>
Simon
  • 1,890
  • 19
  • 26
1

As workaround you can use absolute pathes:

<!-- secondary external storage with path /storage/extSdCard -->
<root-path path="/storage/extSdCard/Android/data/YOUR_PACKAGE/files/" name="extSdCard" />

<!-- secondary external storage with path /storage/sdcard1  -->
<root-path path="/storage/sdcard1/Android/data/YOUR_PACKAGE/files/" name="sdcard1" />
danik
  • 796
  • 9
  • 17
  • 1
    With newer devices, external path depends on the SD card inserted as it uses a reference id, so it's impossible to use absolute path. – 3c71 Nov 01 '17 at 07:42
1

So I ended up doing the following:

Try to create to Uri via FileProvider, If it fails due to:

java.lang.IllegalArgumentException: Failed to find configured root that contains

I'm just creating a regular Uri.

Here's my code:

try {
        uri = FileProvider.getUriForFile(context,
                MY_AUTHORITY_STRING,
                imageFile);
    } catch (Exception e) {
        CLog.d(TAG, e);
        uri = Uri.fromFile(imageFile);
    }

I don't know why, but it's working, the FileProvider can't access the file (As it's in secondary external storage) and then the uri is created successfully in the catch clause.

Weird Google...very weird.

Udi Oshi
  • 6,787
  • 7
  • 47
  • 65