In this tutorial, we’ll be developing an application which displays images captured from camera or gallery using FileProvider. We’ve already developed a similar application in the past. But with the introduction of Android Nougat, it gives a runtime crash saying FileUriExposedException.
Table of Contents
FileProvider
FileProvider is a special subclass of ContentProvider which allows sharing of files between application through content URI instead of file://
URI.
Using file://
URI is not the best idea. It gives all apps the permission to access the files once the Storage Permissions are granted.
We somehow need to restrict this such that the user knows the applications with which it would be sharing the files.
For this, we use FileProviders which allow temporary access permissions to the files. Otherwise, we were able to access files from other apps by simply getting their URI from Uri.parse()
Defining FileProvider
To define a FileProvider in our android application, we need to do the following things:
- Define the FileProvider in your AndroidManifest file
- Create an XML file that contains all paths that the FileProvider will share with other applications
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
Create an xml folder inside the res directory.
Add the provider_paths.xml
file in it:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="https://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
Depending on the storage we need to access, we pass the value in the external-path.
Example of other values that can be passed – sdcard
Now let’s write our Version 2.0 Application of Capturing Image from Camera And Gallery that works on Android Nougat and above.
Project Structure
The AndroidManifest.xml with all the permissions looks like:
Code
The code for the activity_main.xml layout is:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<RelativeLayout
android:id="@+id/content_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<ImageView
android:id="@+id/imageView"
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_centerInParent="true"
android:adjustViewBounds="true"
android:scaleType="centerCrop" />
</RelativeLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
app:srcCompat="@android:drawable/ic_menu_camera" />
</android.support.design.widget.CoordinatorLayout>
The code for the MainActivity.java is given below:
package com.journaldev.androidfileprovidercameragallery;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.provider.MediaStore;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import static android.Manifest.permission.CAMERA;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
public class MainActivity extends AppCompatActivity {
Uri picUri;
private ArrayList<String> permissionsToRequest;
private ArrayList<String> permissionsRejected = new ArrayList<>();
private ArrayList<String> permissions = new ArrayList<>();
private final static int ALL_PERMISSIONS_RESULT = 107;
private final static int IMAGE_RESULT = 200;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivityForResult(getPickImageChooserIntent(), IMAGE_RESULT);
}
});
permissions.add(CAMERA);
permissions.add(WRITE_EXTERNAL_STORAGE);
permissions.add(READ_EXTERNAL_STORAGE);
permissionsToRequest = findUnAskedPermissions(permissions);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (permissionsToRequest.size() > 0)
requestPermissions(permissionsToRequest.toArray(new String[permissionsToRequest.size()]), ALL_PERMISSIONS_RESULT);
}
}
public Intent getPickImageChooserIntent() {
Uri outputFileUri = getCaptureImageOutputUri();
List<Intent> allIntents = new ArrayList<>();
PackageManager packageManager = getPackageManager();
Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
List<ResolveInfo> listCam = packageManager.queryIntentActivities(captureIntent, 0);
for (ResolveInfo res : listCam) {
Intent intent = new Intent(captureIntent);
intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
intent.setPackage(res.activityInfo.packageName);
if (outputFileUri != null) {
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
}
allIntents.add(intent);
}
Intent galleryIntent = new Intent(Intent.ACTION_GET_CONTENT);
galleryIntent.setType("image/*");
List<ResolveInfo> listGallery = packageManager.queryIntentActivities(galleryIntent, 0);
for (ResolveInfo res : listGallery) {
Intent intent = new Intent(galleryIntent);
intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
intent.setPackage(res.activityInfo.packageName);
allIntents.add(intent);
}
Intent mainIntent = allIntents.get(allIntents.size() - 1);
for (Intent intent : allIntents) {
if (intent.getComponent().getClassName().equals("com.android.documentsui.DocumentsActivity")) {
mainIntent = intent;
break;
}
}
allIntents.remove(mainIntent);
Intent chooserIntent = Intent.createChooser(mainIntent, "Select source");
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, allIntents.toArray(new Parcelable[allIntents.size()]));
return chooserIntent;
}
private Uri getCaptureImageOutputUri() {
Uri outputFileUri = null;
File getImage = getExternalFilesDir("");
if (getImage != null) {
outputFileUri = Uri.fromFile(new File(getImage.getPath(), "profile.png"));
}
return outputFileUri;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
ImageView imageView = findViewById(R.id.imageView);
if (requestCode == IMAGE_RESULT) {
String filePath = getImageFilePath(data);
if (filePath != null) {
Bitmap selectedImage = BitmapFactory.decodeFile(filePath);
imageView.setImageBitmap(selectedImage);
}
}
}
}
private String getImageFromFilePath(Intent data) {
boolean isCamera = data == null || data.getData() == null;
if (isCamera) return getCaptureImageOutputUri().getPath();
else return getPathFromURI(data.getData());
}
public String getImageFilePath(Intent data) {
return getImageFromFilePath(data);
}
private String getPathFromURI(Uri contentUri) {
String[] proj = {MediaStore.Audio.Media.DATA};
Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable("pic_uri", picUri);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// get the file url
picUri = savedInstanceState.getParcelable("pic_uri");
}
private ArrayList<String> findUnAskedPermissions(ArrayList<String> wanted) {
ArrayList<String> result = new ArrayList<String>();
for (String perm : wanted) {
if (!hasPermission(perm)) {
result.add(perm);
}
}
return result;
}
private boolean hasPermission(String permission) {
if (canMakeSmores()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED);
}
}
return true;
}
private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", null)
.create()
.show();
}
private boolean canMakeSmores() {
return (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1);
}
@TargetApi(Build.VERSION_CODES.M)
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case ALL_PERMISSIONS_RESULT:
for (String perms : permissionsToRequest) {
if (!hasPermission(perms)) {
permissionsRejected.add(perms);
}
}
if (permissionsRejected.size() > 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (shouldShowRequestPermissionRationale(permissionsRejected.get(0))) {
showMessageOKCancel("These permissions are mandatory for the application. Please allow access.",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissionsRejected.toArray(new String[permissionsRejected.size()]), ALL_PERMISSIONS_RESULT);
}
}
});
return;
}
}
}
break;
}
}
}
Following is are the methods which get the content uri from the filepath provided that the FileProvider has granted the appropriate permissions:
The output of the above application in action is given below:
This brings an end to this tutorial. You can download the project from the link below:
When i choose the pic from google photos or drive file path returns Null
i have enable all permissions
Please reply as soon as possible
Any update on this issue?
Hi
When i choose the pic from google photos or drive file path returns Null
i have enable all permissions
Please reply as soon as possible
Thanks
You state:
“By using FileProvider in your app, you do not need to ask user to grant WRITE_EXTERNAL_STORAGE permission everytime.”
However, your manifest distinctly shows WRITE_EXTERNAL_STORAGE
WOW cool!
I’ve always ran into errors when using the FileProvider instead of using the method you’ve used when getting the file uri.
I think I’m gonna try your method.
Hello, I have the code running on my machine, but what I need is to be able to give the name to the image that is captured from the camera. in the code “profile.png” appears, if I create a consecutive and attached the + “. png” does not work ….. why is that? and what can I do to make it work.
in this drive image will not open
Hi Anupam,
Its nice exmaple.
After going through FileProvider multiple examples I would like to know that:
catgetExternalFilesDir() – stores in Internal Storage (wont require to ask WRITE permission at runtime)
getExternalStoragePublicDirectory() – stores in SD card (that case needed to ask WRITE permission at runtime )
Is it correct? If yes then is it optional in above example to ask for WRITE permission at runtime
Thanks
If i want to select multiple images from gallery then where should i put this ( intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);) code
Could you help me how to set image size not more than 500 kb
How to know whether the image has saved or not? pls help..