ZOFTINO.COM android and web dev tutorials

Android Capture Image from Camera and Save

In this post, you can learn how to provide a feature in your app that allows user to capture pictures using device camera and save them on the local storage or cloud storage. To capture picture from your app using device camera, you need to use either camera API to build component that interact with camera or use existing camera app using which your app can capture camera images.

Let’s see how to capture pictures using existing camera app and save them in gallery and to cloud storage.

Capturing Image from Camera

First in manifest file declare that your app uses camera by using uses-feature element. This is to let external entity like play store know that the app uses camera hardware. Notice that required attribute is set to false because we will disable the feature if device doesn’t have camera so that the app can be installed on devices which don’t have camera to let users use other features of app.

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

Then send intent from your activity to start activity in camera application which allows user to capture pictures. We will start camera activity using startActivityForResult method so that calling app can know once photo capture is complete.

In the intent you need to send complete path of a file where you want the camera app to save the captured image. Let’s store pictures in public pictures directory which can be obtained by calling getExternalStoragePublicDirectory() passing DIRECTORY_PICTURES to it. To access public directory, we need to request for READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE permissions.

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

Below code shows how to create a unique file name for picture.

 String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String pictureFile = "ZOFTINO_" + timeStamp;
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(pictureFile,  ".jpg", storageDir);
pictureFilePath = image.getAbsolutePath();
return image; 

Using FileProvider, get file URI, add it to intent as extra and then start activity.

 Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (cameraIntent.resolveActivity(getPackageManager()) != null) {
    startActivityForResult(cameraIntent, REQUEST_PICTURE_CAPTURE);

    File pictureFile = null;
    try {
        pictureFile = getPictureFile();
    } catch (IOException ex) {
        Toast.makeText(this,
                "Photo file can't be created, please try again",
                Toast.LENGTH_SHORT).show();
        return;
    }
    if (pictureFile != null) {
        Uri photoURI = FileProvider.getUriForFile(this,
                "com.zoftino.android.fileprovider",
                pictureFile);
        cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
        startActivityForResult(cameraIntent, REQUEST_PICTURE_CAPTURE);
    }
} 

Then define FileProvider in manifest file.

 <provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.zoftino.android.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_list"></meta-data>
</provider> 

Define list of file paths for FileProvider in xml file and save it in res/xml folder. Since we want to save only in public picture directory, we’ll just add path of it to xml.

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

In your activity, provide implementation for onActivityResult method so that the activity will know that the photo is captured and saved and do further actions such as displaying the captured image in ImageView.

 @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_PICTURE_CAPTURE && resultCode == RESULT_OK) {
        File imgFile = new  File(pictureFilePath);
        if(imgFile.exists())            {
            image.setImageURI(Uri.fromFile(imgFile));
        }
    }
} 

Once photo is captured, camera app will be closed and focus will be returned to the calling activity. But on some devices, camera app sends the result to calling activity without finishing itself meaning the camera app will continue to be active after sending the result to calling activity. In this case, user will have to close the app to get back to your app.

Saving Picture to Cloud Storage

Captured pictures using camera app from your app can be saved to Firebase cloud storage. To learn how to setup firebase and use firebase cloud storage, please see Firebase cloud storage tutorial.

In this example, I used unique identifier to identify user pictures on cloud storage. But the best way to restrict access to pictures on cloud storage is by using authentication. Please see firebase authentication example.

After receiving result from camera activity, photo will be saved to cloud storage.

 private void addToCloudStorage() {
    File f = new File(pictureFilePath);
    Uri picUri = Uri.fromFile(f);
    final String cloudFilePath = deviceIdentifier + picUri.getLastPathSegment();

    FirebaseStorage firebaseStorage = FirebaseStorage.getInstance();
    StorageReference storageRef = firebaseStorage.getReference();
    StorageReference uploadeRef = storageRef.child(cloudFilePath);

    uploadeRef.putFile(picUri).addOnFailureListener(new OnFailureListener(){
        public void onFailure(@NonNull Exception exception){
            Log.e(TAG,"Failed to upload picture to cloud storage");
        }
    }).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>(){
        @Override
        public void onSuccess(UploadTask.TaskSnapshot taskSnapshot){
            Toast.makeText(CapturePictureActivity.this,
                    "Image has been uploaded to cloud storage",
                    Toast.LENGTH_SHORT).show();
        }
    });
} 

You can store file path in firestore database which can be used to provide an option in your app to allow user to view list of pictures and download or view selected picture. Please see Firebase cloud storage tutorial to know how to do that.

Saving Picture in Gallery

If you save your picture in your app private folder and want to make few pictures public, one of the ways to do is by saving the pictures in gallery. Below code show how to save pictures in gallery after it is captured by camera app.

Intent galleryIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(pictureFilePath);
Uri picUri = Uri.fromFile(f);
galleryIntent.setData(picUri);
this.sendBroadcast(galleryIntent);

Example Output

android notification channel settings

Activity

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;

import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageReference;
import com.google.firebase.storage.UploadTask;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

public class CapturePictureActivity extends AppCompatActivity{
    private static final String TAG = "CapturePicture";
    static final int REQUEST_PICTURE_CAPTURE = 1;
    private ImageView image;
    private String pictureFilePath;
    private FirebaseStorage firebaseStorage;
    private String deviceIdentifier;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_capture_img);

        image = findViewById(R.id.picture);

Button captureButton = findViewById(R.id.capture);
captureButton.setOnClickListener(capture);
if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
    captureButton.setEnabled(false);
}

        findViewById(R.id.save_local).setOnClickListener(saveGallery);
        findViewById(R.id.save_cloud).setOnClickListener(saveCloud);

        firebaseStorage = FirebaseStorage.getInstance();
        getInstallationIdentifier();
    }

    private View.OnClickListener capture = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
                if(getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
                    sendTakePictureIntent();
                }
        }
    };
    private void sendTakePictureIntent() {

        Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        cameraIntent.putExtra( MediaStore.EXTRA_FINISH_ON_COMPLETION, true);
        if (cameraIntent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(cameraIntent, REQUEST_PICTURE_CAPTURE);

            File pictureFile = null;
            try {
                pictureFile = getPictureFile();
            } catch (IOException ex) {
                Toast.makeText(this,
                        "Photo file can't be created, please try again",
                        Toast.LENGTH_SHORT).show();
                return;
            }
            if (pictureFile != null) {
                Uri photoURI = FileProvider.getUriForFile(this,
                        "com.zoftino.android.fileprovider",
                        pictureFile);
                cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
                startActivityForResult(cameraIntent, REQUEST_PICTURE_CAPTURE);
            }
        }
    }
    private File getPictureFile() throws IOException {
        String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
        String pictureFile = "ZOFTINO_" + timeStamp;
        File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        File image = File.createTempFile(pictureFile,  ".jpg", storageDir);
        pictureFilePath = image.getAbsolutePath();
        return image;
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_PICTURE_CAPTURE && resultCode == RESULT_OK) {
            File imgFile = new  File(pictureFilePath);
            if(imgFile.exists())            {
                image.setImageURI(Uri.fromFile(imgFile));
            }
        }
    }
    //save captured picture in gallery
    private View.OnClickListener saveGallery = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            addToGallery();
        }
    };
    private void addToGallery() {
        Intent galleryIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
        File f = new File(pictureFilePath);
        Uri picUri = Uri.fromFile(f);
        galleryIntent.setData(picUri);
        this.sendBroadcast(galleryIntent);
    }
    //save captured picture on cloud storage
    private View.OnClickListener saveCloud = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            addToCloudStorage();
        }
    };
    private void addToCloudStorage() {
        File f = new File(pictureFilePath);
        Uri picUri = Uri.fromFile(f);
        final String cloudFilePath = deviceIdentifier + picUri.getLastPathSegment();

        FirebaseStorage firebaseStorage = FirebaseStorage.getInstance();
        StorageReference storageRef = firebaseStorage.getReference();
        StorageReference uploadeRef = storageRef.child(cloudFilePath);

        uploadeRef.putFile(picUri).addOnFailureListener(new OnFailureListener(){
            public void onFailure(@NonNull Exception exception){
                Log.e(TAG,"Failed to upload picture to cloud storage");
            }
        }).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>(){
            @Override
            public void onSuccess(UploadTask.TaskSnapshot taskSnapshot){
                Toast.makeText(CapturePictureActivity.this,
                        "Image has been uploaded to cloud storage",
                        Toast.LENGTH_SHORT).show();
            }
        });
    }
    protected synchronized String getInstallationIdentifier() {
        if (deviceIdentifier == null) {
            SharedPreferences sharedPrefs = this.getSharedPreferences(
                    "DEVICE_ID", Context.MODE_PRIVATE);
            deviceIdentifier = sharedPrefs.getString("DEVICE_ID", null);
            if (deviceIdentifier == null) {
                deviceIdentifier = UUID.randomUUID().toString();
                SharedPreferences.Editor editor = sharedPrefs.edit();
                editor.putString("DEVICE_ID", deviceIdentifier);
                editor.commit();
            }
        }
        return deviceIdentifier;
    }
}

Layout

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_margin="8dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/picture"
        android:layout_width="wrap_content"
        android:layout_height="400dp"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:orientation="horizontal">
        <Button
            android:id="@+id/capture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="10dp"
            style="@style/Widget.AppCompat.Button.Colored"
            android:text="Capture"/>
        <Button
            android:id="@+id/save_local"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="10dp"
            style="@style/Widget.AppCompat.Button.Colored"
            android:text="Save Gallary" />
        <Button
            android:id="@+id/save_cloud"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/Widget.AppCompat.Button.Colored"
            android:text="Save Cloud"/>
    </LinearLayout>

</LinearLayout>