12

I have an issue where I'm trying to attach multiple files from my internal storage to an email intent by providing them using a FileProvider declared in my manifest and granting the read uri permissions. Here is my code.

Manifest

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

Intent Creation

        emailIntent = new Intent(android.content.Intent.ACTION_SEND_MULTIPLE);
        emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL,new String[] {"randomemailaddress"});

    ArrayList<Uri> uris = new ArrayList<Uri>();
    File directory = getFilesDir();
    File[] list = directory.listFiles();
    for (File f : list) {
        if (f.getName().contains("log") && f.getName().contains(".txt"))
            uris.add(FileProvider.getUriForFile(this, "com.company.example.logprovider",f));
    }

    emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);

The Issue

This works fine on two devices running Jelly Bean however it does not work for two devices running gingerbread.

The exception I am getting is as follows.

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.google.android.gm/com.google.android.gm.ComposeActivity}: java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord{40b2db08 3885:com.google.android.gm/10110} (pid=3885, uid=10110) requires null or null
E/AndroidRuntime( 3885):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1659)
E/AndroidRuntime( 3885):    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1675)
E/AndroidRuntime( 3885):    at android.app.ActivityThread.access$1500(ActivityThread.java:121)
E/AndroidRuntime( 3885):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:943)
E/AndroidRuntime( 3885):    at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime( 3885):    at android.os.Looper.loop(Looper.java:138)
E/AndroidRuntime( 3885):    at android.app.ActivityThread.main(ActivityThread.java:3701)
E/AndroidRuntime( 3885):    at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime( 3885):    at java.lang.reflect.Method.invoke(Method.java:507)
E/AndroidRuntime( 3885):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:878)
E/AndroidRuntime( 3885):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:636)
E/AndroidRuntime( 3885):    at dalvik.system.NativeStart.main(Native Method)
E/AndroidRuntime( 3885): Caused by: java.lang.SecurityException: Permission Denial: opening provider android.support.v4.content.FileProvider from ProcessRecord{40b2db08 3885:com.google.android.gm/10110} (pid=3885, uid=10110) requires null or null
E/AndroidRuntime( 3885):    at android.os.Parcel.readException(Parcel.java:1322)
E/AndroidRuntime( 3885):    at android.os.Parcel.readException(Parcel.java:1276)
E/AndroidRuntime( 3885):    at android.app.ActivityManagerProxy.getContentProvider(ActivityManagerNative.java:1882)
E/AndroidRuntime( 3885):    at android.app.ActivityThread.getProvider(ActivityThread.java:3365)
E/AndroidRuntime( 3885):    at android.app.ActivityThread.acquireProvider(ActivityThread.java:3390)
E/AndroidRuntime( 3885):    at android.app.ContextImpl$ApplicationContentResolver.acquireProvider(ContextImpl.java:1728)
E/AndroidRuntime( 3885):    at android.content.ContentResolver.acquireProvider(ContentResolver.java:754)
E/AndroidRuntime( 3885):    at android.content.ContentResolver.query(ContentResolver.java:262)
E/AndroidRuntime( 3885):    at com.google.android.gm.ComposeArea.addAttachment(ComposeArea.java:736)
E/AndroidRuntime( 3885):    at com.google.android.gm.ComposeArea.initFromExtras(ComposeArea.java:699)
E/AndroidRuntime( 3885):    at com.google.android.gm.ComposeActivity.initFromExtras(ComposeActivity.java:1482)
E/AndroidRuntime( 3885):    at com.google.android.gm.ComposeActivity.finishOnCreateAfterAccountSelected(ComposeActivity.java:1020)
E/AndroidRuntime( 3885):    at com.google.android.gm.ComposeActivity.onCreate(ComposeActivity.java:259)
E/AndroidRuntime( 3885):    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
E/AndroidRuntime( 3885):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1623)
E/AndroidRuntime( 3885):    ... 11 more

My Thoughts

So somewhere down the line, the FLAG_GRANT_READ_URI_PERMISSION flag I am adding to the intent is not working?

I also believe the issue may be because I am attaching the uris to the intent as an EXTRA_STREAM and for some reason, older Android versions don't pick this up? (have not found any documentation on this yet).

Any help is appreciated.

StuStirling
  • 15,601
  • 23
  • 93
  • 150
  • did you tryed this http://stackoverflow.com/questions/18249007/how-to-use-support-fileprovider-for-sharing-content-to-other-apps – PedroAGSantos Feb 10 '14 at 15:22
  • Yes I did, however `grantUriPermission` only allows specific packages to be able to access the uri – StuStirling Feb 10 '14 at 15:28
  • How about http://stackoverflow.com/questions/21275898/securityexception-with-granturipermission-when-sharing-a-file-with-fileprovider? – Eric Woodruff Feb 12 '14 at 16:48

1 Answers1

9
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);
}
Thai Tran
  • 9,815
  • 7
  • 43
  • 64
Digvesh Patel
  • 6,503
  • 1
  • 20
  • 34
  • 4
    You should link to the original answer instead of copying it: http://stackoverflow.com/a/18332000/3519951 – JM Lord Sep 07 '16 at 19:46
  • 1
    @JMLord there is nothing worse than a good answer, with a dead link. I'd say copy it, and give credit for where it came from, would be a better option. – Brill Pappin Apr 02 '18 at 13:41
  • 3
    @BrillPappin Good point! Linkrot is indeed a problem. However, giving credit alone can't be of much use if we can't find the original post... Well then, why not link + summarize + credit? – JM Lord Apr 04 '18 at 12:44