Permission Denial while sharing file with FileProvider

14,842

Solution 1

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

Solution 2

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"));
}

Solution 3

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));

Share:
14,842
Paweł Bęza
Author by

Paweł Bęza

Hi ;) I'm Paul and I am currently studying Computer Science at Warsaw University of Technology.

Updated on June 14, 2022

Comments

  • Paweł Bęza
    Paweł Bęza almost 2 years

    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.

  • lukas hansen
    lukas hansen about 4 years
    Awesome! Key takeaway I initially missed: Pass the chooser, not the intent to queryIntentActivities()
  • NoobCoderChick
    NoobCoderChick about 4 years
    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
    NoobCoderChick about 4 years
    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
  • Rahul Matte
    Rahul Matte over 3 years
    Does anybody comment for more details please
  • t0m
    t0m over 3 years
    And what about intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); where uris is ArrayList?
  • Peter Csala
    Peter Csala over 3 years
    Please provide some explanation why do you think your proposed solution might help the OP.
  • Admin
    Admin over 3 years
    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().
  • Peter Csala
    Peter Csala over 3 years
    Please amend your answer to include what you have written in the comment.
  • Admin
    Admin over 3 years
    oh, sorry I'm New Here. I will do
  • Derek K
    Derek K about 3 years
    Thanks! Perfect solution without the need of querying activities.
  • Samuel T. Chou
    Samuel T. Chou over 2 years
    The FLAG_GRANT_PERSISTABLE_URI_PERMISSION solution does not solve the problem in Android 11.
  • Samuel T. Chou
    Samuel T. Chou over 2 years
    What is the path used in ClipData.Item ? I tried uri.path, but exception still exists.
  • Nit
    Nit about 2 years
    Oops, my bad @SamuelT.Chou, you should directly put the Uri when creating the ClipData.Item(). I have edited my answer with the fix.
  • iadcialim24
    iadcialim24 about 2 years
    This works for target 31