Sharing files through Intents (part 2): fixing the permissions before Lollipop

Lorenzo Quiroli
Feb 20, 2017 · 5 min read

Since I wrote my piece about how to use FileProvider to enhance security and add compatibility with Nougat, I discovered some issues happening in older versions and now it’s time to fix them.

If you’ve updated your app to drop the old file:// Uri and you’ve been trying to adopt FileProvider instead, you might have encountered some crashes on devices running on KitKat or lower versions of Android. I wouldn’t be too surprised if you could even find a device with Lollipop affected by this issue.

So, what’s going on? Why is it happening? As usual let’s start from the stacktrace:

02-16 05:41:07.477 8063-8063/ E/AndroidRuntime: FATAL EXCEPTION: main
Process:, PID: 8063
java.lang.IllegalStateException: Could not execute method of the activity
at android.view.View$1.onClick(
at android.view.View.performClick(
at android.view.View$
at android.os.Handler.handleCallback(
Caused by: java.lang.SecurityException: Permission Denial: opening provider from ProcessRecord{9d5abad8} (pid=8063, uid=10031) that is not exported from uid 10057
at android.os.Parcel.readException(
at android.os.Parcel.readException(
at android.content.ContentResolver.acquireUnstableProvider(
at android.content.ContentResolver.openAssetFileDescriptor(
at android.content.ContentResolver.openOutputStream(
at android.content.ContentResolver.openOutputStream(
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(

I’ve highlighted the important part which makes it quite clear that this is about a permission issue. Weird, why do we have the right permissions on Lollipop and higher but not on KitKat and lower?

At this point you might try to simply add permission with the line:


But not even adding that line is going to fix the issue. You might be tempted to set android:exported="true" in the provider declaration in the AndroidManifest.xml but the documentation clearly states that the provider does not need to be public.

The bad solution

context.grantUriPermission(String toPackage, Uri uri, int flags);

Where toPackage is the application Id of the receiver app and flags is the type of permission you want to set. You could then revoke those permissions by calling:

context.revokeUriPermission(Uri uri, int modeFlags);

This solution raises another problem: we don’t know which app is going to receive our Intent(it will be up to the user to pick its favourite) and granting permission to all of them capable of receiving the Intent wouldn’t definitely be an elegant solution.

Going deeper

If set, the recipient of this Intent will be granted permission to perform read operations on the URI in the Intent’s data and any URIs specified in its ClipData. When applying to an Intent’s ClipData, all URIs as well as recursive traversals through data or other ClipData in Intent items will be granted; only the grant flags of the top-level Intent are used.

If you Google it and try to find any possible information about this issue you might find some comments by Ian Lake on StackOverflow: since Android 4.1 Jelly Bean (API Level 16) there’s a new hidden method called migrateExtraStreamToClipData in the Intent class which is called to migrate some specific extras (depending on your Intent action) to ClipData and grants permissions to it. For example if you use ACTION_SEND your URI will be granted the FLAG_GRANT_READ_URI_PERMISSION without having you worried to do so.

In our case we are using MediaStore.ACTION_IMAGE_CAPTURE as Intent action. If we take a look at the code of the migrateExtraStreamToClipData method in Lollipop we can see that it migrates the MediaStore.EXTRA_OUTPUT Uri to ClipData and it grants both read and write permissions on it. The same isn’t happening in KitKat which is the reason of the permission issue.

How can we fix it in previous versions?

We just add an additional if statement:

We are basically checking if the version is lower or equal to Lollipop and if it is we create a new ClipData object from the Uri by calling the static method ClipData.newRawUri(String label, Uri uri). That’s exactly what migrateExtraStreamToClipData does for us in newer versions.

This solution works on Jelly Bean and higher, because there’s no setClipData method in Ice Cream Sandwich. If you really need to support API Level 15 or lower you might want to generate the old good file:// Uri.

Why are we including Lollipop?

Wrapping up

Even if the receiver app is accessing the Uri via the extra and not the ClipData, everything should be fine since the permission are applied to the Uri, not to the field itself. There are some exceptions to the rule where the Uri receives some permissions automatically thanks to migrateExtraStreamToClipData:

  • Intent.ACTION_SEND: since API level 16 the EXTRA_STREAM Uri is granted the FLAG_GRANT_READ_URI_PERMISSION.
  • Intent.ACTION_SEND_MULTIPLE: since API level 16 all the EXTRA_STREAM Uris are granted FLAG_GRANT_READ_URI_PERMISSION.

I’ve also updated my GitHub repo with the fixes to make it work on API Level 17 (previously minSdk was set to 21).

Thanks to the CommonsBlog for this piece, which helped me getting a proper understanding of the issue and to everyone else providing feedbacks on the previous article.

Thanks to Roberto Orgiu.

Lorenzo Quiroli

Written by

Android Engineer @ busuu