Share Files Using FileProvider on Android

share-files-using-fileprovider-0

Several weeks ago I was given a task to open an internal PDF file in any PDF reader application on an Android phone. I thought it would be straightforward, but things turned out to be complicated. Google’s documentation on FileProvider proved to be confusing and lacking concrete examples. Nevertheless, I knew I had to use ContentProvider to tackle the issue.

What is FileProvider

ContentProvider is an Android component which encapsulates data and provides it to other applications. It is required only if you need to share data between multiple applications. For example, the contacts data is shared with other applications using ContactsProvider which is a subclass of ContentProvider.

FileProvider is a subclass of ContentProvider. While ContentProvider is a component that enables you to securely share any kind of data, FileProvider is used specifically for sharing the app’s internal files. The FileProvider class is part of the v4 Support Library so make sure to include it in your project.

To make FileProvider work follow these three steps:

  • Define the FileProvider in your AndroidManifest file
  • Create an XML file that contains all paths that the FileProvider will share with other applications
  • Bundle a valid URI in the Intent and activate it

Defining a FileProvider

Defining a FileProvider

To define a FileProvider inside your AndroidManifest you need to be familiar with these attributes and elements:

  • android:authorities
  • android:exported
  • android:grantUriPermissions
  • android:name
  • <meta-data> subelement

If all of these seem extremely familiar, you’ll find your way around FileProvider a bit easier, otherwise I’ve prepared a detailed description of each attribute and its purpose.

android:authorities

You must define at least one unique authority. Android System keeps a list of all providers and it distinguishes them by authority. Authority defines the FileProvider just like the application ID defines an Android application.

In general, the Android System uses a specific URI scheme for ContentProviders. The scheme is defined as content://<authority>/<path> so the system will know which ContentProvider is requested by matching the URI’s authority with the ContentProvider’s authority.

android:exported

This attribute can easily be misused because its name is misleading. To understand this attribute, think of your FileProvider as a room with locked doors. If you set the value to true, you’ve basically opened your doors to everyone. Everything will work from your point of view, but you’ve just created a huge security issue as every other app will be able to use your FileProvider without being given permission.

This can teach you to never program by coincidence and always be aware of the side-effects of your code. Also, always define this attribute because the default value on SDK 16 and lower is true.

android:grantUriPermissions

If we continue thinking of a FileProvider as a locked room, then this attribute is used to give a temporary one-time key to an external app. This attribute allows you to securely share your app’s internal storage. All you have to do is add either FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION to the Intent that activates the component to open your app’s internal file. To use these flags set their value to true.

The <provider> element can also have <grant-uri-permission> subelements. The only difference being that by using the attribute you can share anything inside your app’s internal storage, while subelements allow you to choose a specific data subset for sharing. To use subelements instead, set the value to false.

<meta-data> subelement

Create file-path XML

This subelement must be defined when using the FileProvider. You have to define a path to the XML file which contains all data paths your FileProvider can share with external apps.

The XML file must have the \<paths> element as its root. The \<paths> element must have at least one subelement which can be one of the following:

  • <files-path/> – internal app storage, Context#getFilesDir()
  • <cache-path/> – internal app cache storage, Context#getCacheDir()
  • <external-path/> – public external storage, Environment.getExternalStorageDirectory()
  • <external-files-path/> – external app storage, Context#getExternalFilesDir(null)
  • <external-cache-path/> – external app cache storage, Context#getExternalCacheDir()

You might have noticed that they vary according to the app’s directory which they define.

Each element must have a path and a name attribute. The path attribute defines the subdirectory you want to share and it does not support wildcards. The name attribute is used for security reasons and it will replace your subdirectory name with its value.

android:name

We set this value to android.support.v4.content.FileProvider.

Bundle valid URI

Once you have defined a FileProvider in your AndroidManifest file, you are finally ready to use it. To share a file you have to create an Intent and give it a valid URI. The URI is generated using the FileProvider class.

Code implementation

AndroidManifest.xml

	<provider
    android:name="android.support.v4.content.FileProvider"
    android:grantUriPermissions="true"
    android:exported="false"
    android:authorities="${applicationId}">

    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_provider_paths"/>

</provider>

Notice that I’m using the app’s ID for authority. That’s because I have multiple flavors in the project and they can be installed on the device at the same time. Android System won’t let you install multiple applications with the same FileProvider so each flavor needs an unique authority.

file_provider_paths.xml

	<paths>
    <cache-path name="cache" path="/" />
    <files-path name=”files” path=”/” />
</paths>

By defining paths like this, I allow the FileProvider to share all files that are inside the app’s internal cache and files directory.

Use your FileProvider

	// create new Intent
Intent intent = new Intent(Intent.ACTION_VIEW);

// set flag to give temporary permission to external app to use your FileProvider
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

// generate URI, I defined authority as the application ID in the Manifest, the last param is file I want to open
String uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, file);

// I am opening a PDF file so I give it a valid MIME type
intent.setDataAndType(uri, "application/pdf");

// validate that the device can open your File!
PackageManager pm = getActivity().getPackageManager();
if (intent.resolveActivity(pm) != null) {
    startActivity(intent);
}

Once you understand how it works, implementing your own FileProvider is truly simple.

The complexity of the problem is not the code itself, but the documentation and understanding of how everything is interconnected. I hope this article will help you implement the FileProvider for your own use case and make the official documentation easier to read.