Use Android's FileProvider to get rid of the Storage Permission

March 14, 2017 | Romain Guefveneu | 4-minute read

When you need to share a file with other apps, the easiest way could be to use the external storage as a temporary place where to save this file. For example, if you need to take a picture with a camera app, you need to specify a file where the camera app will save the picture, and using external storage might be tempting.

However this solution has many drawbacks:

  • You lose control of your file

Because you put your file in a public directory, you can’t safely delete it nor control which app can read and modify it.

  • You have to ask WRITE_EXTERNAL_STORAGE permission

This could make many users afraid and may lead to a bad UX with runtime permissions.

  • External storage quickly becomes a mess

Since you can’t safely delete your shared files, you let them in the external storage forever.

Use a FileProvider

You may already use ContentProvider to share data with other apps, you can do the same with FileProvider to share files!

FileProvider is part of Support Library, available for all Android versions starting 2.3. The main goal of this API is to temporary open a private file to some targeted apps: you keep the file in your private folder, and let some other apps read or even write it via a secured ContentProvider. Permissions are revoked when your activity is destroyed.

Implementation

Add support lib dependency

In your app build.gradle, add this dependency:

compile 'com.android.support:support-v4:<version>'

Specify available folders

Create an xml file (for example file_provider_paths.xml) in xml resources folder:

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="shared" path="shared/"/>
</paths>

Define a Provider

In your ApplicationManifest.xml, add this provider inside application node:

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="<your provider authority>"
    android:exported="false"
    android:grantUriPermissions="true">
  <meta-data
      android:name="android.support.FILE_PROVIDER_PATHS"
      android:resource="@xml/file_provider_paths"/>
</provider>

Just set your android:authorities, like com.drivy.android.myfileprovider, and link the created xml resource file in android:resource

ProTip: Use ${applicationId}in android:authorities to automatically use your package name: ${applicationId}.myfileprovider

Share a file

First thing to do: get the shared file’s Uri

Uri sharedFileUri = FileProvider.getUriForFile(this, <your provider auhtority>, sharedFile);

Use the same provider authority as in your ApplicationManifest.xml. The Uri will looks like this: content://com.drivy.android.myfileprovider/shared/myfile.jpg

You can now create a chooser intent:

ShareCompat.IntentBuilder intentBuilder = ShareCompat.IntentBuilder.from(this).addStream(sharedFileUri);

And start it:

Intent chooserIntent = intentBuilder.createChooserIntent();
startActivity(chooserIntent);

That’s it!

One last thing: Legacy support

You need to manually grant permission for older Android versions.

Grant permission for intent

Before sharing your file, you’ll have to manually grant the permission (read and/or write), for all applications targeted with your intent. Indeed, you can’t know which one the user will choose to share the file with.

final PackageManager packageManager = context.getPackageManager();
final List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolvedIntentInfo : activities) {
  final String packageName = resolvedIntentInfo.activityInfo.packageName;
  context.grantUriPermission(packageName, uri, permissions);
}

Revoke permissions on activity destroy

We can assume that, when returning back to your app and leaving the activity, the shared file has already been copied by the targeted app, and is not required anymore. You can revoke all permissions.

context.revokeUriPermission(fileUri, permissions);

Conclusion

FileProvider is a really convenient and elegant way to get rid of WRITE_EXTERNAL_STORAGE, I encourage you to use it: your app will be better without extra permissions.

Sources

  1. Github project
  2. Setting Up File Sharing - Android Developers
View openings 👍  Like this post? Join Drivy's engineering team!