How to use support FileProvider for sharing content to other apps?
Asked Answered
D

11

171

I'm looking for a way to correctly share (not OPEN) an internal file with external application using Android Support library's FileProvider.

Following the example on the docs,

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.example.android.supportv4.my_files"
    android:grantUriPermissions="true"
    android:exported="false">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/my_paths" />
</provider>

and using ShareCompat to share a file to other apps as follows:

ShareCompat.IntentBuilder.from(activity)
.setStream(uri) // uri from FileProvider
.setType("text/html")
.getIntent()
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

does not work, since the FLAG_GRANT_READ_URI_PERMISSION only grants permission for the Uri specified on the data of the intent, not the value of the EXTRA_STREAM extra (as was set by setStream).

I tried to compromise security by setting android:exported to true for the provider, but FileProvider internally checks if itself is exported, when so, it throws an exception.

Derwon answered 15/8, 2013 at 8:34 Comment(2)
+1 this seems to be a truly original question - there is nothing on Google or StackOverflow, as far as I could tell.Katzenjammer
Here's a post to get a basic FileProvider setup going using a minimal github example project: https://mcmap.net/q/49630/-fileprovider-throws-exception-on-geturiforfile . It provides steps for which files to copy over (without making changes) into a local standalone projectAminopyrine
S
178

Using FileProvider from support library you have to manually grant and revoke permissions(at runtime) for other apps to read specific Uri. Use Context.grantUriPermission and Context.revokeUriPermission methods.

For example:

//grant permision for app with package "packegeName", eg. before starting other app via intent
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

//revoke permisions
context.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

As a last resort, if you can't provide package name you can grant the permission to all apps that can handle specific intent:

//grant permisions for all apps that can handle given intent
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
...
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName;
    context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}

Alternative method according to the documentation:

  • Put the content URI in an Intent by calling setData().
  • Next, call the method Intent.setFlags() with either FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION or both.
  • Finally, send the Intent to another app. Most often, you do this by calling setResult().

    Permissions granted in an Intent remain in effect while the stack of the receiving Activity is active. When the stack finishes, the
    permissions are automatically removed. Permissions granted to one
    Activity in a client app are automatically extended to other
    components of that app.

Btw. if you need to, you can copy source of FileProvider and change attachInfo method to prevent provider from checking if it is exported.

Sulfanilamide answered 20/8, 2013 at 9:50 Comment(9)
Using the grantUriPermission method we need to provide the package name of the app we want to grant the permission to. However, when sharing, usually we don't know what application the sharing destination is.Knack
What if we don't know the package name and just want other apps to be able to open itPersons
In that case try to use intent.setData() and intent.setFlags(last part of my answer after edit).Sulfanilamide
Can you please provide a working code for the latest solution @Lecho?Orthostichy
Is there a bug open tracking why the support FileProvider doesn't work with setFlags, but does work with grantUriPermission? Or is this not a bug, in which case how is this not a bug?Noted
The FileProvider works for me with setFlags since OS 4.4. For older devices I use grantUriPermission.Reclamation
In addition to this, I found that the grantUriPermission call didn't work for an intent created using the ShareCompat but it did when manually creating the Intent.Persons
Please note: if you use the approach with intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) keep in mind that you must add the data FIRST to the intent and only then you add a flag with permission. Otherwise, it won't work.Cantone
this wont work since android 11 change package visibilityScapegrace
S
58

Fully working code sample how to share file from inner app folder. Tested on Android 7 and Android 5.

AndroidManifest.xml

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

xml/provider_paths

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path
        name="share"
        path="external_files"/>
</paths>

Code itself

    File imagePath = new File(getFilesDir(), "external_files");
    imagePath.mkdir();
    File imageFile = new File(imagePath.getPath(), "test.jpg");

    // Write data in your file

    Uri uri = FileProvider.getUriForFile(this, getPackageName(), imageFile);

    Intent intent = ShareCompat.IntentBuilder.from(this)
                .setStream(uri) // uri from FileProvider
                .setType("text/html")
                .getIntent()
                .setAction(Intent.ACTION_VIEW) //Change if needed
                .setDataAndType(uri, "image/*")
                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

   startActivity(intent);
Subversion answered 21/9, 2016 at 14:30 Comment(9)
The use of the ShareCompat.IntentBuilder and the read URI permission finally did it for me.Townie
Breaks for other versions of AndroidBlowhole
@OliverDixon: which ones? I tested it on Android 6.0 and 9.0.Incurve
This is the only answer on using the FileProvider that works. The other answers get the intent handling wrong (they don't use ShareCompat.IntentBuilder), and that intent is not openable by external apps in any meaningful way. I've spent literally most of the day on this nonsense until finally finding your answer.Incurve
@VioletGiraffe, I think, other people tested their answers on many devices. Yes, we don't use ShareCompat and everything works right. Look that ShareCompat is noted in the question. In my case I check versions (4.4 and below). You tested on 6.0 and 9.0, that is not enough.Thee
@VioletGiraffe I am also not using the ShareCompat but mine works. My issue was that I was granting the read permissions to the intent of my chooser by mistake, when I needed to grant read permissions to my intent BEFORE it was passed to the chooser intent, as well as the chooser. I hope that makes sense. Just make sure you are granting read permission to the correct intent.Rebate
@Ryan, thanks for the info! Did you test it on Android 9? That's where I've been getting issues with other answers, there were fewer problems on older Android versions.Incurve
@VioletGiraffe Yep I did it on Android 9 on a Galaxy S8 on Verizon (if any of that matters lol). It was a bit of a pain to migrate because I am working on an app that I haven't touched in over a year. Updates broke quite a bit haha.Rebate
I've tried both, using ShareCompat and using a generic Intent. Both worked in my case. Tried on API 23, 27, 29 and 30. All worked. I personally like the normal Intent way of sharing more, because it creates a nice destination picker on newer Android versions. Whereas using the ShareCompat capability, you only get the generic and boring destination picker.Zymogen
R
25

This solution works for me since OS 4.4. To make it work on all devices I added a workaround for older devices. This ensures that always the safest solution is used.

Manifest.xml:

    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="com.package.name.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>

file_paths.xml:

<paths>
    <files-path name="app_directory" path="directory/"/>
</paths>

Java:

public static void sendFile(Context context) {
    Intent intent = new Intent(Intent.ACTION_SEND);
    intent.setType("text/plain");
    String dirpath = context.getFilesDir() + File.separator + "directory";
    File file = new File(dirpath + File.separator + "file.txt");
    Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file);
    intent.putExtra(Intent.EXTRA_STREAM, uri);
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    // Workaround for Android bug.
    // grantUriPermission also needed for KITKAT,
    // see https://code.google.com/p/android/issues/detail?id=76683
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
        List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            context.grantUriPermission(packageName, attachmentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
    }
    if (intent.resolveActivity(context.getPackageManager()) != null) {
        context.startActivity(intent);
    }
}

public static void revokeFileReadPermission(Context context) {
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
        String dirpath = context.getFilesDir() + File.separator + "directory";
        File file = new File(dirpath + File.separator + "file.txt");
        Uri uri = FileProvider.getUriForFile(context, "com.package.name.fileprovider", file);
        context.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }
}

The permission is revoked with revokeFileReadPermission() in the onResume and onDestroy() methods of the Fragment or the Activity.

Reclamation answered 5/10, 2015 at 13:54 Comment(2)
This solution worked for me but only after I added intent.setDataAndType(uri, "video/*"); BTW missing attachmentUri variable is uriJeane
Do you mean that file won't change from onResume to onDestroy?Thee
A
16

Since as Phil says in his comment on the original question, this is unique and there is no other info on SO on in google, I thought I should also share my results:

In my app FileProvider worked out of the box to share files using the share intent. There was no special configuration or code necessary, beyond that to setup the FileProvider. In my manifest.xml I placed:

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.my.apps.package.files"
        android:exported="false"
        android:grantUriPermissions="true" >
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/my_paths" />
    </provider>

In my_paths.xml I have:

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="files" path="." />
</paths>

In my code I have:

    Intent shareIntent = new Intent();
    shareIntent.setAction(Intent.ACTION_SEND);
    shareIntent.setType("application/xml");

    Uri uri = FileProvider.getUriForFile(this, "com.my.apps.package.files", fileToShare);
    shareIntent.putExtra(Intent.EXTRA_STREAM, uri);

    startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.share_file)));

And I am able to share my files store in my apps private storage with apps such as Gmail and google drive without any trouble.

Alcibiades answered 3/9, 2013 at 4:1 Comment(3)
Sadly this doesn't work. That variable fileToShare only works if the file exists on an SDcard or external file system. The point of using FileProvider is so that you can share content from the internal system.Hereld
This does seem to work properly with local storage files (on Android 5+). But I get issues on Android 4.Diacetylmorphine
this code is not working in Api lolipopMakowski
S
16

As far as I can tell this will only work on newer versions of Android, so you will probably have to figure out a different way to do it. This solution works for me on 4.4, but not on 4.0 or 2.3.3, so this will not be a useful way to go about sharing content for an app that's meant to run on any Android device.

In manifest.xml:

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.mydomain.myapp.SharingActivity"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

Take careful note of how you specify the authorities. You must specify the activity from which you will create the URI and launch the share intent, in this case the activity is called SharingActivity. This requirement is not obvious from Google's docs!

file_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="just_a_name" path=""/>
</paths>

Be careful how you specify the path. The above defaults to the root of your private internal storage.

In SharingActivity.java:

Uri contentUri = FileProvider.getUriForFile(getActivity(),
"com.mydomain.myapp.SharingActivity", myFile);
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("image/jpeg");
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(shareIntent, "Share with"));

In this example we are sharing a JPEG image.

Finally it is probably a good idea to assure yourself that you have saved the file properly and that you can access it with something like this:

File myFile = getActivity().getFileStreamPath("mySavedImage.jpeg");
if(myFile != null){
    Log.d(TAG, "File found, file description: "+myFile.toString());
}else{
    Log.w(TAG, "File not found!");
}
Sural answered 19/1, 2014 at 22:37 Comment(0)
S
9

In my app FileProvider works just fine, and I am able to attach internal files stored in files directory to email clients like Gmail,Yahoo etc.

In my manifest as mentioned in the Android documentation I placed:

<provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.package.name.fileprovider"
        android:grantUriPermissions="true"
        android:exported="false">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepaths" />
    </provider>

And as my files were stored in the root files directory, the filepaths.xml were as follows:

 <paths>
<files-path path="." name="name" />

Now in the code:

 File file=new File(context.getFilesDir(),"test.txt");

 Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE);

 shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,
                                     "Test");

 shareIntent.setType("text/plain");

 shareIntent.putExtra(android.content.Intent.EXTRA_EMAIL,
                                 new String[] {"email-address you want to send the file to"});

   Uri uri = FileProvider.getUriForFile(context,"com.package.name.fileprovider",
                                                   file);

                ArrayList<Uri> uris = new ArrayList<Uri>();
                uris.add(uri);

                shareIntent .putParcelableArrayListExtra(Intent.EXTRA_STREAM,
                                                        uris);


                try {
                   context.startActivity(Intent.createChooser(shareIntent , "Email:").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));                                                      


                }
                catch(ActivityNotFoundException e) {
                    Toast.makeText(context,
                                   "Sorry No email Application was found",
                                   Toast.LENGTH_SHORT).show();
                }
            }

This worked for me.Hope this helps :)

Salvage answered 28/10, 2014 at 13:27 Comment(2)
You da real MVP for posting paths="." Worked for meDrench
Im getting error like "Failed to find configured root that contains /" i have used the same codeFiber
T
6

If you get an image from camera none of these solutions work for Android 4.4. In this case it's better to check versions.

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (intent.resolveActivity(getContext().getPackageManager()) != null) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        uri = Uri.fromFile(file);
    } else {
        uri = FileProvider.getUriForFile(getContext(), getContext().getPackageName() + ".provider", file);
    }
    intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
    startActivityForResult(intent, CAMERA_REQUEST);
}
Thee answered 1/3, 2018 at 16:10 Comment(0)
B
2

grantUriPermission (from Android document)

Normally you should use Intent#FLAG_GRANT_READ_URI_PERMISSION or Intent#FLAG_GRANT_WRITE_URI_PERMISSION with the Intent being used to start an activity instead of this function directly. If you use this function directly, you should be sure to call revokeUriPermission(Uri, int) when the target should no longer be allowed to access it.

So I test and I see that.

  • If we use grantUriPermission before we start a new activity, we DON'T need FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION in Intent to overcome SecurityException

  • If we don't use grantUriPermission. We need to use FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION to overcome SecurityException but

    • Your intent MUST contain Uri by setData or setDataAndType else SecurityException still throw. (one interesting I see: setData and setType can not work well together so if you need both Uri and type you need setDataAndType. You can check inside Intent code, currently when you setType, it will also set uri= null and when you setUri it will also set type=null)
Began answered 15/2, 2021 at 7:53 Comment(1)
Thank you ! I was struggling for hours: the grantUriPermission didn't work in my case. But the flags Intent#FLAG_GRANT_READ_URI_PERMISSION and Intent#FLAG_GRANT_WRITE_URI_PERMISSION worked like a charm.Eleanore
F
1

just to improve answer given above: if you are getting NullPointerEx:

you can also use getApplicationContext() without context

                List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(takePictureIntent, PackageManager.MATCH_DEFAULT_ONLY);
                for (ResolveInfo resolveInfo : resInfoList) {
                    String packageName = resolveInfo.activityInfo.packageName;
                    grantUriPermission(packageName, photoURI, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }
Forcemeat answered 1/1, 2017 at 9:23 Comment(0)
L
1

I want to share something that blocked us for a couple of days: the fileprovider code MUST be inserted between the application tags, not after it. It may be trivial, but it's never specified, and I thought that I could have helped someone! (thanks again to piolo94)

Longwise answered 23/8, 2018 at 11:52 Comment(0)
M
0

All the above answers are correct, I just want to add something that rather than hard-coding it will be better to fetch application id programmatically:

In AndroidManifest:

instead of this: android:authorities="com.example.xxx.fileprovider"

use this android:authorities="${applicationId}.fileprovider"

Programmatically:

instead of this: FileProvider.getUriForFile(this, "com.example.xxx.fileprovider", tempFile)

use this FileProvider.getUriForFile( this, "$packageName.fileprovider", tempFile )

Mears answered 14/12, 2023 at 6:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.