168

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.

Randy Sugianto 'Yuku'
  • 71,383
  • 57
  • 178
  • 228
  • 11
    +1 this seems to be a truly original question - there is *nothing* on Google or *StackOverflow*, as far as I could tell. – Phil Aug 23 '13 at 14:31
  • Here's a post to get a basic FileProvider setup going using a minimal github example project: https://stackoverflow.com/a/48103567/2162226 . It provides steps for which files to copy over (without making changes) into a local standalone project – Gene Bo Jan 04 '18 at 21:34

10 Answers10

177

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.

schnatterer
  • 7,525
  • 7
  • 61
  • 80
Leszek
  • 6,568
  • 3
  • 42
  • 53
  • 31
    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. – Randy Sugianto 'Yuku' Aug 20 '13 at 14:08
  • What if we don't know the package name and just want other apps to be able to open it – StuStirling Feb 10 '14 at 15:25
  • In that case try to use intent.setData() and intent.setFlags(last part of my answer after edit). – Leszek Feb 10 '14 at 20:04
  • 3
    Can you please provide a working code for the latest solution @Lecho? – StErMi Mar 31 '14 at 07:45
  • 2
    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? – nmr Sep 26 '14 at 00:28
  • The FileProvider works for me with setFlags since OS 4.4. For older devices I use grantUriPermission. – Oliver Kranz Oct 05 '15 at 14:29
  • 1
    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. – StuStirling Mar 24 '16 at 14:54
  • 3
    _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. – Kirill Karmazin Oct 15 '18 at 12:19
  • 3
    this wont work since android 11 change package visibility – ruif3r Dec 07 '21 at 22:45
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);
Community
  • 1
  • 1
Divers
  • 9,531
  • 7
  • 45
  • 88
  • 3
    The use of the `ShareCompat.IntentBuilder` and the read URI permission finally did it for me. – Cord Rehn Oct 29 '16 at 23:13
  • Breaks for other versions of Android – Oliver Dixon May 11 '17 at 08:57
  • @OliverDixon: which ones? I tested it on Android 6.0 and 9.0. – Violet Giraffe Jun 18 '19 at 18:26
  • 8
    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. – Violet Giraffe Jun 18 '19 at 18:27
  • @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. – CoolMind Sep 19 '19 at 06:38
  • 1
    @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. – Ryan Sep 30 '19 at 22:48
  • @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. – Violet Giraffe Oct 01 '19 at 07:49
  • @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. – Ryan Oct 03 '19 at 19:25
  • 1
    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. – Akito Nov 21 '21 at 18:04
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.

Community
  • 1
  • 1
Oliver Kranz
  • 3,741
  • 2
  • 26
  • 31
  • 1
    This solution worked for me but only after I added intent.setDataAndType(uri, "video/*"); BTW missing attachmentUri variable is uri – EdgarK Jul 21 '16 at 16:58
  • Do you mean that `file` won't change from `onResume` to `onDestroy`? – CoolMind Feb 19 '18 at 13:22
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.

Luke Sleeman
  • 1,636
  • 1
  • 16
  • 27
  • 11
    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. – Keith Connolly Oct 30 '13 at 00:18
  • This does seem to work properly with local storage files (on Android 5+). But I get issues on Android 4. – Peterdk Aug 30 '16 at 01:10
  • this code is not working in Api lolipop – EAS Feb 09 '22 at 09:07
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!");
}
Intrications
  • 16,782
  • 9
  • 50
  • 50
Rasmusob
  • 508
  • 3
  • 13
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 :)

Adarsh Chithran
  • 218
  • 3
  • 6
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);
}
CoolMind
  • 26,736
  • 15
  • 188
  • 224
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)
Linh
  • 57,942
  • 23
  • 262
  • 279
  • 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. – Ahmed Jul 19 '22 at 07:57
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);
                }
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)

Eric Draven
  • 259
  • 3
  • 7