53

I am trying to share file with FileProvider. I checked that file is shared properly with apps like Gmail, Google Drive etc. Even though following exception is thrown:

2019-08-28 11:43:03.169 12573-12595/com.example.name E/DatabaseUtils: Writing exception to parcel
    java.lang.SecurityException: Permission Denial: reading androidx.core.content.FileProvider uri content://com.example.name.provider/external_files/Android/data/com.example.name/files/allergy_report.pdf from pid=6005, uid=1000 requires the provider be exported, or grantUriPermission()
        at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:729)
        at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:602)
        at android.content.ContentProvider$Transport.query(ContentProvider.java:231)
        at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:104)
        at android.os.Binder.execTransactInternal(Binder.java:1021)
        at android.os.Binder.execTransact(Binder.java:994)

provider:

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_provider_paths" />
</provider>

file_provider_paths.xml

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

Sharing Intent

Intent intentShareFile = new Intent(Intent.ACTION_SEND);
File fileWithinMyDir = new File(targetPdf);

if (fileWithinMyDir.exists()) {
    intentShareFile.setType("application/pdf");
    Uri uri = FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID + ".provider", fileWithinMyDir);
    intentShareFile.putExtra(Intent.EXTRA_STREAM, uri);
    intentShareFile.putExtra(Intent.EXTRA_SUBJECT, "Sharing File...");
    intentShareFile.putExtra(Intent.EXTRA_TEXT, "Sharing File...");
    intentShareFile.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    startActivity(Intent.createChooser(intentShareFile, "Share File"));
}

Hopefully you can point out my mistake why this exceptions is thrown when it seems like apps are granted permission properly and sharing works as it should be.

EDIT:

I found that the problem lies in line:

startActivity(Intent.createChooser(intentShareFile, "Share File"));

When I changed it simply to

startActivity(intentShareFile);

However it displays a little bit different layout for picking application. But still I cannot figure out why original chooser is not working.

Matt Ke
  • 3,599
  • 12
  • 30
  • 49
Paweł Bęza
  • 1,375
  • 1
  • 13
  • 23

4 Answers4

109

Sorry about the late response. I solved it like this:

Intent chooser = Intent.createChooser(intentShareFile, "Share File");

List<ResolveInfo> resInfoList = this.getPackageManager().queryIntentActivities(chooser, PackageManager.MATCH_DEFAULT_ONLY);

for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName;
    this.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}

startActivity(chooser);

This helped me: Permission Denial with File Provider through intent

Iakovos
  • 1,842
  • 4
  • 25
  • 30
  • 11
    Awesome! Key takeaway I initially missed: Pass the chooser, not the intent to queryIntentActivities() – lukas hansen Feb 23 '20 at 21:34
  • 3
    OMG, I have been dealing with this for days! This solved my issue and I have no idea why I just copy and pasted it haha. Gonna read through what just saved my butt! Thanks! – NoobCoderChick Feb 24 '20 at 04:43
  • Whats weird is I have 4 image views on the same activity and when you click the first one (any of them) the camera app comes up, you take the pic and it saves to the image view just fine...the second one I click, camera comes up, you take the pic and when you select select the checkmark it crashes the app. What's also super weird is the path file that is in Android docs does not work and I have to use "/" as the path...idk – NoobCoderChick Feb 24 '20 at 04:45
  • Does anybody comment for more details please – Rahul Matte Oct 23 '20 at 23:31
  • 4
    And what about `intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);` where uris is `ArrayList`? – t0m Dec 16 '20 at 11:08
  • 2
    This works for target 31 – iadcialim24 Apr 17 '22 at 09:38
  • Awesome!!!... It worked for me. But I'd like someone explain me what it does. Thanks a lot!! – Jimmy ALejandro Aug 24 '22 at 16:42
  • Why setting temporary permission is not enough? `intentShareFile.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);` I see the doc ```Caution: Calling setFlags() is the only way to securely grant access to your files using temporary access permissions. Avoid calling Context.grantUriPermission() method for a file's content URI, since this method grants access that you can only revoke by calling Context.revokeUriPermission(). ``` https://developer.android.com/training/secure-file-sharing/share-file#GrantPermissions – nAkhmedov Sep 07 '22 at 09:16
  • caution! google don't recommend use this. see official documentation for details – deviant May 15 '23 at 11:07
21

There is also a way to grant the URI permission through the Intent flag, without doing in manually with grantUriPermission() and by keeping the usage of Intent.createChooser().

The Intent.createChooser() documentation states:

If the target intent has specified FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION, then these flags will also be set in the returned chooser intent, with its ClipData set appropriately: either a direct reflection of getClipData() if that is non-null, or a new ClipData built from getData().

Therefore, if the original Intent has the Uri set in its ClipData or in setData(), it should work as wanted.

In your example, the ACTION_SEND Intent supports the Uri set through setClipData() as of Jelly Bean (Android 4.1).

Long story short, here is a working example of your code:

Intent intentShareFile = new Intent(Intent.ACTION_SEND);
File fileWithinMyDir = new File(targetPdf);

if(fileWithinMyDir.exists()) {
    String mimeType = "application/pdf";
    String[] mimeTypeArray = new String[] { mimeType };
    
    intentShareFile.setType(mimeType);
    Uri uri = FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID + ".provider", fileWithinMyDir);
    
    // Add the uri as a ClipData
    intentShareFile.setClipData(new ClipData(
        "A label describing your file to the user",
        mimeTypeArray,
        new ClipData.Item(uri)
    ));
    
    // EXTRA_STREAM is kept for compatibility with old applications
    intentShareFile.putExtra(Intent.EXTRA_STREAM, uri);
    
    intentShareFile.putExtra(Intent.EXTRA_SUBJECT,
            "Sharing File...");
    intentShareFile.putExtra(Intent.EXTRA_TEXT, "Sharing File...");
    intentShareFile.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    startActivity(Intent.createChooser(intentShareFile, "Share File"));
}
Nit
  • 1,115
  • 9
  • 9
8

In addition to the answer of Nit above, it is possible to set the ClipData directly with the to be shared uri as follows.

intent.setClipData(ClipData.newRawUri("", uri));

This prevents the security exception when presenting the intent chooser.

If you want to share multiple uris, you can set the clipdata with multiple uris to prevent the security exception. To summarize:


Intent intent = new Intent();
intent.setType(mimeType);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

if (uris.size() == 0) { 
   return;
}
else if (uris.size() == 1) {
   Uri uri = uris.get(0);
   intent.setAction(Intent.ACTION_SEND);
   intent.putExtra(Intent.EXTRA_STREAM, uri);
   intent.setClipData(ClipData.newRawUri("", uri));   
}
else {
  intent.setAction(Intent.ACTION_SEND_MULTIPLE);
  intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
  ClipData clipData = ClipData.newRawUri("", uris.get(0));

  for (int i = 1; i < uris.size(); i++) { 
      Uri uri = uris.get(i); 
      clipData.addItem(new ClipData.Item(uri));
  }
  intent.setClipData(clipData);

}

startActivity(Intent.createChooser(intent, title));

Harmen
  • 81
  • 1
  • 3
1

This worked for me.

    Intent sharableIntent = new Intent();
    sharableIntent.setAction(Intent.ACTION_SEND);
    sharableIntent.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION );

    Uri imageUri = Uri.parse(ImgLoc);
    File imageFile = new File(String.valueOf(imageUri));
    Uri UriImage = FileProvider.getUriForFile(context, Author, imageFile);

    sharableIntent.setType("image/*");
    sharableIntent.putExtra(Intent.EXTRA_STREAM, UriImage);
    sharableIntent.putExtra(Intent.EXTRA_TITLE, title);
    sharableIntent.putExtra(Intent.EXTRA_TEXT, body);

    Intent chooser = Intent.createChooser(sharableIntent, "Chooser Title");
    chooser.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
    context.startActivity(chooser);

createChooser Intent.createChooser():Builds a new ACTION_CHOOSER Intent that wraps the given target intent, also optionally supplying a title. If the target intent has specified FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION, then these flags will also be set in the returned chooser intent, with its ClipData set appropriately: either a direct reflection of getClipData() if that is non-null, or a new ClipData built from getData()

  • 3
    Please provide some explanation why do you think your proposed solution might help the OP. – Peter Csala Dec 17 '20 at 15:08
  • Intent.createChooser():Builds a new ACTION_CHOOSER Intent that wraps the given target intent, also optionally supplying a title. If the target intent has specified FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION, then these flags will also be set in the returned chooser intent, with its ClipData set appropriately: either a direct reflection of getClipData() if that is non-null, or a new ClipData built from getData(). – MOHO LEARNIN G Dec 17 '20 at 17:13
  • Please amend your answer to include what you have written in the comment. – Peter Csala Dec 17 '20 at 17:21
  • 1
    oh, sorry I'm New Here. I will do – MOHO LEARNIN G Dec 17 '20 at 17:35
  • 1
    The `FLAG_GRANT_PERSISTABLE_URI_PERMISSION` solution does not solve the problem in Android 11. – Samuel T. Chou Feb 16 '22 at 08:31