
Tectes
Android Nougat (SDK 24) enforces the StrictMode
API policy that prohibits exposing file://
URIs outside your app.
As a result, sharing a file://
URI outside of your application will throw an FileUriExposedException.
If your app supports Android 7.0 and newer versions, make sure to generate a content://
URI instead and share it with the appropriate permissions. FileProvider is a custom class extending from ContentProvider that facilitates this process.
There are 3 main steps to follow in order to share a content://
URI:
FileProvider can only generate URI for files that are stored in directories specified by your application. This is done via an XML file with <paths>
root element and one to multiple <path>
children elements depending on the type of file you intend to share and the storage area where they’ll be in.
<paths>
<files-path name="my_files" path="files" />
<files-path name="my_other_files" path="other_files" />
<cache-path name="my_cache" path="cache" />
<external-path name="my_external" path="external" />
<external-files-path name="my_external_files" path="external_files" />
<external-cache-path name="my_external_cache" path="external_cache" />
<external-media-path name="my_external_media" path="external_media" />
</paths>
There are 6 type of paths:
<files-path>
are files stored in the files/
directory of your application<cache-path>
are files stored in the cache/
directory of your application<external-path>
are files stored in the external storage area<external-files-path>
are files stored in the external storage area dedicated to the app<external-cache-path>
are files stored in the external cache storage area dedicated to the app<external-media-path>
are files stored in external media area dedicated to the appEach element has two attributes:
path
is the path of the directory the files will be stored inname
is a virtual name for the path (this is a security measure to prevent exposing the files’ paths).If you are sharing files from multiple locations, you’ll have to define as many path elements:
<paths>
<files-path name="documents" path="files/documents" />
<files-path name="forms" path="files/forms" />
</paths>
Your custom provider must extend from FileProvider
and its constructor takes a resourceId
which correspond to the XML file you’ve created in the previous step
class CustomFileProvider: FileProvider(R.xml.file_paths)
Add a <provider>
tag to your manifest (as a child of <application>
)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application ...>
<provider
android:name=".CustomFileProvider"
android:authorities="${applicationId}.customfileprovider"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider>
...
</application>
</manifest>
Make sure that:
android:name
set to your custom providerandroid:authorities
attribute. It can be one or multiple authorities separated by a semi-colon.grantUriPermissions
is set to true
. This allows the content provider to temporary grant permission to the data (note that if you set this to false
you can control Uri permission at a more granular level)<meta-data>
tag as a child to <provider>
and set
android:name
attribute to android.support.FILE_PROVIDER_PATHS
android:resource
attribute to the your path XML file.Sharing the file is quite simple once the previous configurations are done:
content://
URI from the file using FileProvidercontent://
URI using the createChooser
IntentHere’s a code sample for creating and sharing a file
enum class PathType {
FILES,
CACHE,
EXTERNAL,
EXTERNAL_FILES,
EXTERNAL_CACHE,
}
fun createDummyFile(context: Context, pathType: PathType = PathType.FILES): File {
val timestamp = System.currentTimeMillis()
val path = when (pathType) {
PathType.FILES -> File(context.filesDir, "files")
PathType.CACHE -> File(context.cacheDir, "cache")
PathType.EXTERNAL -> File(Environment.getExternalStorageDirectory(), "external")
PathType.EXTERNAL_FILES -> File(context.getExternalFilesDir(null), "external_files")
PathType.EXTERNAL_CACHE -> File(context.externalCacheDir, "external_cache")
}
try {
path.mkdirs()
} catch (_: SecurityException) {}
val file = File(path, "$timestamp.txt")
file.printWriter().use { out ->
out.println("hello world")
}
return file
}
fun shareFile(file: File, context: Context) {
val uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.customfileprovider", file)
val intent = Intent(Intent.ACTION_SEND).apply {
putExtra(Intent.EXTRA_STREAM, uri)
setDataAndType(uri, "text/html")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
val shareIntent = Intent.createChooser(intent, "Share file")
try {
context.startActivity(shareIntent)
} catch (e: ActivityNotFoundException) {
Log.w("MainActivity", "failed to initiate `createChooser`", e)
}
}
Here’s a list of things that are easy to miss when working with FileProvider:
<provider>
tag is a child of the <application>
tag. Also don’t forget the <meta-data>
tag.<meta-data>
tag has the properties android:name
and android.resource
. Specifically, do not replace android.resource
with android.value
FileProvider.getUriForFile()
.FileProvider
directly instead of creating your custom provider, however “this is not reliable and will causes crashes on some devices”
<files-path /> : File(Context.filesDir, "")
<cache-path /> : File(Context.cacheDir, "")
<external-path /> : File(Environment.getExternalStorageDirectory(), "")
<external-files-path /> : File(Context.getExternalFilesDir(null), "")
<external-cache-path /> : File(Context.externalCacheDir, "")