Android FileProvider is a sub class of ContentProvider. It is used to share files between different android apps. It is implemented in android v4 Support Library. So before use it please make sure that you have include supported library in android project build.gradle file as below.
compile 'com.android.support:support-v4:26.+' or compile 'com.android.support:appcompat-v7:26.+'
1. FileProvider Benefits.
Using FileProvider you can make your app files more secure in following ways.
- You can fully control which file to share to which app more accurately.
- Your app do not need to ask user to grant WRITE_EXTERNAL_STORAGE permission always. This can make your app more user friendly.
- When your app destroy, the shared folder permission will also be revoked. Then other apps can not access those files later.
2. Create FileProvider Steps.
- Declare FileProvider provider component in AndroidManifest.xml file.
- Create a share folder xml file to indicate which folder will be shared.
2.1 Define FileProvider In AndroidManifest.xml.
Please add below provider definition in Android project AndroidManifest.xml first to use FileProvider.
<provider android:authorities="< your provider authority >" android:name="android.support.v4.content.FileProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/shared_file_paths" /> </provider>
FileProvider Attributes Explain.
- android:authorities : Authority values must be unique, it is used by android system to distinguish all providers. It is also used in FileProvider accessing uri such as content://<authority>/<path> because FileProvider is a sub class of ContentProvider. You can use ${applicationId} as authority value, it will use your app package name automatically. And each app package name in android is unique.
- android:name : This attribute value must be android.support.v4.content.FileProvider
- android:exported : This attribute value must be false, because if set it to true then all other apps can use this FileProvider without grant permission. So this will raise security risk. If you set it’s value to true a SecurityException(“Provider must not be exported”) will be thrown.
- android:grantUriPermissions : This attribute value must be true. This means you need to grant uri read / write permission to other apps that can access your shared files. You can use intent setFlags method to grant FileProvider access permission to other apps like below.
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
If you set grantUriPermissions value to false, it will throw SecurityException(“Provider must grant uri permissions”).
- meta-data sub element : The provider meta-data sub element is used to specify the shared folder definition xml file. It has two attributes android:name and android:resource.android:name value must be android.support.FILE_PROVIDER_PATHS and android:resource value is a xml file name, the name can be any value but it should be located in app / res / xml folder.
2.2 Define Shared Folder In Xml Resource File.
In our example the shared folder definition xml file name is shared_file_paths.xml, below is it’s content. You can see comments for each element explanation.
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Share folder under device root directory. The base folder is new File("/") --> <root-path name="root" path="" /> <!-- Share folder under internal file folder. The base folder is context.getFilesDir() --> <files-path name="internal_files" path="" /> <!-- Share folder under internal cache folder. The base folder is context.getCacheDir() --> <cache-path name="internal_cache" path="" /> <!-- Share folder under public external storage folder.The base folder is Environment.getExternalStorageDirectory()--> <external-path name="external" path="Android/data/com.dev2qa.example/cache/" /> <!-- Share folder under app specific external file folder.The base folder is context.getExternalFilesDirs()--> <external-files-path name="external_files" path="" /> <!-- Share folder under app specific external cache folder.The base folder is context.getExternalCacheDirs()--> <external-cache-path name="external_cache" path="" /> </paths>
From above example, we can see that you can share both root, internal, external folders in android. To share different type folder use different xml tag.
Each tag has two attributes, name and path, name attribute value is the path value in the FileProvider uri when it is accessed by other apps.
For example if name value is “share”, then the FileProvider uri should be something like content://< authority >/share/abc.png. This way the real local file path will not exposed to other apps.
3. How To Use FileProvider In Code.
Below is the general usage about how to use FileProvider.
- Create a new Intent object.
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
- Set intent flag to grant read / write uri permissions to startup activity.
cameraIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
- Use FileProvider to get shared file uri.
Uri outputImgUri = FileProvider.getUriForFile(context, < fileprovider authority >, file);
- Start the activity use above intent. Then the started activity can write the shared files through FileProvider.
startActivityForResult(cameraIntent, REQUEST_CODE_TAKE_PICTURE);
- To operate files through FileProvider, you should use ContentResolver and the file uri together.
// Get content resolver. ContentResolver contentResolver = getContentResolver(); // Use the content resolver to open camera taken image input stream through image uri. InputStream inputStream = contentResolver.openInputStream(outputImgUri);
- Use below code to grant uri permissions to all activities which will be started by this intent.
// Get all activity that listen to this intent in a list. Context context = getApplicationContext(); PackageManager packageManager = context.getPackageManager(); List<ResolveInfo> resInfoList = packageManager.queryIntentActivities(cameraIntent , PackageManager.MATCH_DEFAULT_ONLY); // Loop the activity list. int size = resInfoList.size(); for(int i=0;i<size;i++) { ResolveInfo resolveInfo = resInfoList.get(i); // Get activity package name. String packageName = resolveInfo.activityInfo.packageName; // Grant uri permission to each activity. context.grantUriPermission(packageName, outputImgUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION); }
Reference
My application is composed of package A and package B and must use the same files. Why Google developpers do not offer this very simple possibility : say in manifest A that package B has access to files, and vice-versa in manifest B ?