Android’s New Image Capture from a Camera using File Provider

Prudhvi Raj Kumar
AndroidPub
Published in
3 min readJan 31, 2018

--

From the latest version of Android, Nougat — Capturing an image through a default camera is exposing a FileUriExposedException. Google has launched a more generic way in order to capture the image through a File Provider which reduces the risk of exposing a content:// uri to another application. I have pushed the source code on github-Source and published one video here.

So, Let’s start how can we achieve capturing an image from the camera and getting the file path and load an image in an ImageView.

First, you need to add the permission in the manifest file as

<manifest>
...
<uses-feature android:name="android.hardware.camera"
android:required="true" />

...
</manifest>

In the permission, the android:required=”true” is to tell the Google’s play to filter all the Android devices which have camera function. If we use android:required=”false”, we need to check for the camera via programmatically.

So, next query for all the activities in the device which will handle the CAPTURE_REQUEST intent.

private static final int REQUEST_CAPTURE_IMAGE = 100;

private void openCameraIntent() {
Intent pictureIntent = new Intent(
MediaStore.ACTION_IMAGE_CAPTURE
);
if(pictureIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(pictureIntent,
REQUEST_CAPTURE_IMAGE);
}
}

Now, get the result from onActivityResult() and load the image as following:

@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode == REQUEST_CAPTURE_IMAGE &&
resultCode == RESULT_OK) {
if (data != null && data.getExtras() != null) {
Bitmap imageBitmap = (Bitmap) data.getExtras().get("data");
mImageView.setImageBitmap(imageBitmap);
}
}
}

But if you want the imagePath to send the image to a server as a MultiPartRequest, you need to save the file in a directory as temporary file. From that file obtain the path and will send to the server.

Now, add the permission in the manifest to save the image file in to the external storage as

<manifest>
...
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

...
</manifest>

Now let’s create a function to generate a random file name with .jpg extension in the external files directory.

String imageFilePath;private File createImageFile() throws IOException {
String timeStamp =
new SimpleDateFormat("yyyyMMdd_HHmmss",
Locale.getDefault()).format(new Date());
String imageFileName = "IMG_" + timeStamp + "_";
File storageDir =
getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);

imageFilePath = image.getAbsolutePath();
return image;
}

With this method available to create a file for the photo, you can now create and invoke the Intent like this:

private static final int REQUEST_CAPTURE_IMAGE = 100;

private void openCameraIntent() {
Intent pictureIntent = new Intent(
MediaStore.ACTION_IMAGE_CAPTURE);
if(pictureIntent.resolveActivity(getPackageManager()) != null){
//Create a file to store the image
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
// Error occurred while creating the File
...
}
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(this, "com.example.android.provider", photoFile);
pictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
photoURI
);
startActivityForResult(pictureIntent,
REQUEST_CAPTURE_IMAGE);
}
}
}

If you observe the above code carefully, you may notice where the “com.example.android.provider” came? So, from the latest applications target API level 24, to access the files, they need to add a FileProvider Content Provider to their manifest file. Just follow the steps as:

<application>
...
<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/file_paths">
</meta-data>
</provider>
...
</application>

The ${applicationId} automatically gets the package name of your android application. On the above Statement also, we can use context.getPackageName() instead of hard-coded text of package name.

The “@xml/file_paths” we need to add it separately in the resources directory. Just create a android resource directory of type xml and create a file named file_paths.xml in that and copy the code exactly with the following.

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images"
path="Android/data/com.example.package.name/files/Pictures" />
</paths>

Note: The path component corresponds to the path that is returned by getExternalFilesDir() when called with Environment.DIRECTORY_PICTURES.

To obtain the imageFilePath we don’t need to use the same as what we have used earlier, a new approach is :

@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode == REQUEST_IMAGE_CAPTURE) {
//don't compare the data to null, it will always come as null because we are providing a file URI, so load with the imageFilePath we obtained before opening the cameraIntent
Glide.with(this).load(imageFilePath).into(mImageView);
// If you are using Glide.
}
}

If you want to handle, what if the user cancelled the camera intent without taking a picture like, though we have the imagePath, but it’s not a valid image because the user has not taken the picture. So, just compare the resultCode integer to confirm whether the user cancelled or taken the picture.

if (resultCode == Activity.RESULT_OK) {
Glide.with(this).load(imageFilePath).into(mImageView);
}
else if(resultCode == Activity.RESULT_CANCELLED) {
// User Cancelled the action
}

--

--

Prudhvi Raj Kumar
AndroidPub

Developer, Explorer, Dreamer, Optimist, Painter, Engineer moving forward for new opportunities which fill more success stories in my life ❤