Dealing with Permissions when sharing files (Android M)

You finished your app, you added all code necessary to ask for permissions on the fly for Android M, (you set SDK target to 23, of course) and you published your new cool app that can share some content pretty much everywhere.

And then, out of nowhere, people (on Android M) start complaining about not being able to share on some apps (like Gmail, Hangouts, Instagram or Facebook) and you begin wondering what you did wrong.

Probably you are sharing the content using the path to a file instead of Content Providers? If that your case, please continue reading, if not, I will really appreciate your feedback about what’s next.

About file permissions

When you try to share some custom content on other apps, you have to take into account the permissions that other apps were granted, specifically the Storage Permission.

This issue became apparent when trying to share pictures, videos and animated GIFs from my app to GMail (and later I discovered that it happens with Hangout, Instagram and Facebook) *

If you try to share a file stored in your private folder (/package/files) or even on a public folder (/Pictures/appName) the target has to have the Storage Permission (or it will fail, some silently, some gratefully, only a few just crashed)

About Content Providers

Content Providers can be used to share content between apps. Utilizing a content provider is a safe alternative to sharing local device files and prompting the user for the Storage group permission.

Content Providers are easy to implement, but there is still a catch when using it for sharing files (I need to dig more into it, but there is a workaround for it)

Real Case Example

I used to share content using the path of my newly created file (that worked for me, I never worry about it until now)

String file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + “/YOUR_FOLDER/myFile.jpg”;
Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse(“file://” + file));
startActivity(intent);

That code won’t work on Android M sharing to GMail (that doesn’t have the Storage Permission) even if your app has Storage Permission

Implementing Content Providers

So, the “proper” way to share your file (maybe I was doing it wrong from the beginning) is to user Content Providers

Let’s start by adding our Content Provider to our AndroidManifest.xml

<provider
android:name=”android.support.v4.content.FileProvider”
android:authorities=”{authority_name}
android:exported=”false”
android:grantUriPermissions=”true”> <! — Does it even work? →
<meta-data
android:name=”android.support.FILE_PROVIDER_PATHS”
android:resource=”@xml/file_provider_paths” />
</provider>

And then create the file_providers_paths.xml file in your xml folder insider res

<paths xmlns:android=”http://schemas.android.com/apk/res/android">
<external-path name=”images” path=”Pictures/YOUR_FOLDER” />
</paths>

That should be enough for creating the Content Provider, let’s see how we can share our files now.

String file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + “/YOUR_FOLDER/myFile.jpg”;
Intent intent = new Intent(Intent.ACTION_SENDTO);
Uri fileUri = FileProvider.getUriForFile(context, {authority_name}, new File(file));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(Intent.EXTRA_STREAM, fileUri);
startActivity(intent);

This piece of code will share your file using your Content Provider, but we are not done yet. Looks like giving permission using grantUriPermissions in the AndroidManifest.xml file doesn’t work, even setting intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) doesn’t work either, so we need to give explicit permission for this to work.

What options do we have?

First, we can add permission to the specific app that is going to access the file (hardly when you can share to almost every installed app)

context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);

If you don’t know the package name of the target app, we can always loop and giver permission to every app that will handle this intent. (This is exactly what I’m doing as it was the only thing that worked for me)

List<ResolveInfo> resolvedInfoActivities = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo ri : resolvedInfoActivities) {
context.grantUriPermission(ri.activityInfo.packageName,fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}

You should revoke the permissions but I’m not sure when is the right moment to do that

context.revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);

That should be it for sharing files that actually works on Android M.

Hope this post would be useful to all the developers trying to figure out why a simple action as sharing a file can be tricky with the launch of Android M.

* Twitter Case: Somehow they are doing what every app (I’m talking to you Google) should do. ASK FOR PERMISSIONS! If your app (GMail, FB, Instagram) needs to access a file you should ask for Permission, right Google? Why are you, among all, are not doing it? When you try to share a file using a file path on Twitter, the app will ask you for permissions first and then it will attach your file to a new tweet. That’s how it should work.

Bottom line

You should start using Content Providers to share files from your app if you are not doing if right know. Don’t think that this will fix all the issues, but it will help to avoid most of the problems related to permissions when working with 3rd party apps.

Thanks to Carlos Sessa and Erik Kamp for helping me editing this post.