Android CameraX Java Example

Page content

Introduction

In the world of Android, I have felt Camera/Camera2 API is one of the complicated implementation to make it work at our convenience.

First it was Camera API, then it was deprecated as Camera2 API came into picture. Whereas there is no much less in difficulty working with the same.

Finally for us, Android Jetpack brought us CameraX API with which the implementation and working with Android Camera can be done easily and very less possible lines of code. CameraX is built on top of the Camera2 API. In this article, we have a simple tutorial of the implementation of CameraX API with JAVA.

As I have browsed through many blogs and tutorials, there are no tutorial of implementing CameraX with Java mainly according to the latest documentation (1.0.0-beta03). That’s the main reason for this article.

Why late, Let’s jump into the code:

Steps to implement

Implementing CameraX is a simple 5-step process:

  1. Adding Permission in Manifest
  2. Adding required Dependencies
  3. Adding Camera View and Capture Button in XML
  4. Adding Camera M-permission (Realtime permission)
  5. Setting up CameraX in Activity

Step 1: Adding Permission in Manifest

First thing to be done is adding the Camera permission to our application manifest

<uses-permission android:name=”android.permission.CAMERA” />

Add Storage permission along with the camera permission as we are saving the captured picture in this example

Step 2: Adding required dependencies in build.gradle (App level)

def camerax_version = "1.0.0-beta05"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
implementation"androidx.camera:camera-view:1.0.0-alpha12"
implementation "androidx.camera:camera-extensions:1.0.0-alpha12"

Right now CameraX is in beta version — 1.0.0-beta03

Step 3: Adding Camera View (PreviewView) and Capture Button in XML

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FF000000"
    >

    <androidx.camera.view.PreviewView
        android:id="@+id/camera"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="8dp"
        android:contentDescription="@string/preview_area"
        android:importantForAccessibility="no"
        app:layout_constraintBottom_toTopOf="@+id/linearLayout"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <LinearLayout
        android:id="@+id/linearLayout"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_gravity="bottom"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:gravity="center_horizontal"
        android:orientation="horizontal"
        android:weightSum="3"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/camera">

        <androidx.cardview.widget.CardView
            android:id="@+id/capture"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            app:cardBackgroundColor="#ffffff"
            app:cardCornerRadius="18dp">

            <ImageView
                android:id="@+id/captureImg"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:contentDescription="@string/capture"
                android:src="@mipmap/ic_capture" />

        </androidx.cardview.widget.CardView>

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

PreviewView (androidx.camera.view.PreviewView) has been introduced by Android team in CameraX.

As previously in Camera/Camera2 we used TextureView or customized TextureView (AutoFitTextureView).

As per documentation, PreviewView have some limitations:

Using PreviewView has some limitations.

When using PreviewView, you can’t do any of the following things:

  • Create a SurfaceTexture to set on TextureView and PreviewSurfaceProvider.
  • Retrieve the SurfaceTexture from TextureView and set it on PreviewSurfaceProvider.
  • Get the Surface from SurfaceView and set it on PreviewSurfaceProvider.
  • If any of these happen, then the Preview will stop streaming frames to the PreviewView.

Step 4: Adding Camera M-permission (Realtime permission)

private int REQUEST_CODE_PERMISSIONS = 1001;
private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA", "android.permission.WRITE_EXTERNAL_STORAGE"};

Below is a method to request Camera and Storage permission, which will be used in onCreate method of the Activity.

private boolean allPermissionsGranted(){

    for(String permission : REQUIRED_PERMISSIONS){
        if(ContextCompat.checkSelfPermission(getActivity(), permission) != PackageManager.PERMISSION_GRANTED){
            return false;
        }
    }
    return true;
}
@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        if(requestCode == REQUEST_CODE_PERMISSIONS){
            if(allPermissionsGranted()){
                startCamera();
            } else{
                Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show();
                this.finish();
            }
        }
    }
}

Step 5: Setting up CameraX in Activity

As per documentation:

Developers use CameraX to interface with a device’s camera through an abstraction called a use case. The following use cases are currently available:

  • Preview: accepts a surface for displaying a preview, such as a PreviewView.
  • Image analysis: provides CPU-accessible buffers for analysis, such as for machine learning.
  • Image capture: captures and saves a photo.

Use cases can be combined and active concurrently. For example, an app can let the user view the image that the camera sees using a preview use case, have an image analysis use case that determines whether the people in the photo are smiling, and include an image capture use case to take a picture once they are.

In simple words, CameraX currently have three Classes/Use Cases namely Preview, Image Analysis and Image Capture. In these, Preview and Image Capture are mandatory things to be associated whereas Image Analysis is optional.

Creating and Associating the object of these classed/use cases are shown in below code snippet.

So the CameraX Code is as follows.

private Executor executor = Executors.newSingleThreadExecutor();
private void startCamera() {

    final ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);

    cameraProviderFuture.addListener(new Runnable() {
        @Override
        public void run() {
            try {

                ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
                bindPreview(cameraProvider);

            } catch (ExecutionException | InterruptedException e) {
                // No errors need to be handled for this Future.
                // This should never be reached.
            }
        }
    }, ContextCompat.getMainExecutor(this));
}

void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {

    Preview preview = new Preview.Builder()
            .build();

    CameraSelector cameraSelector = new CameraSelector.Builder()
            .requireLensFacing(CameraSelector.LENS_FACING_BACK)
            .build();

    ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
            .build();

    ImageCapture.Builder builder = new ImageCapture.Builder();

    //Vendor-Extensions (The CameraX extensions dependency in build.gradle)
    HdrImageCaptureExtender hdrImageCaptureExtender = HdrImageCaptureExtender.create(builder);

    // Query if extension is available (optional).
    if (hdrImageCaptureExtender.isExtensionAvailable(cameraSelector)) {
        // Enable the extension if available.
        hdrImageCaptureExtender.enableExtension(cameraSelector);
    }

    final ImageCapture imageCapture = builder
            .setTargetRotation(this.getWindowManager().getDefaultDisplay().getRotation())
            .build();
preview.setSurfaceProvider(mPreviewView.createSurfaceProvider());
    Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector, preview, imageAnalysis, imageCapture);

    

    captureImage.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US);
            File file = new File(getBatchDirectoryName(), mDateFormat.format(new Date())+ ".jpg");

            ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file).build();
            imageCapture.takePicture(outputFileOptions, executor, new ImageCapture.OnImageSavedCallback () {
                @Override
                public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                    new Handler().post(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this, "Image Saved successfully", Toast.LENGTH_SHORT).show();
                        }
                    });
                }
                @Override
                public void onError(@NonNull ImageCaptureException error) {
                    error.printStackTrace();
                }
            });
        }
    });
}

The deep explanation on each statement used is available in Official Documentation.

Finally, add this code in your onCreate along with the PreviewView and Capture ImageView declaration and initialization:

if(allPermissionsGranted()){
    startCamera(); //start camera if permission has been granted by user
} else{
    ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
}

Final Code

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.Preview;
import androidx.camera.extensions.HdrImageCaptureExtender;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;

import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;

import com.google.common.util.concurrent.ListenableFuture;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class MainActivity extends AppCompatActivity {

    private Executor executor = Executors.newSingleThreadExecutor();
    private int REQUEST_CODE_PERMISSIONS = 1001;
    private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA", "android.permission.WRITE_EXTERNAL_STORAGE"};

    PreviewView mPreviewView;
    ImageView captureImage;

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

        mPreviewView = findViewById(R.id.previewView);
        captureImage = findViewById(R.id.captureImg);

        if(allPermissionsGranted()){
            startCamera(); //start camera if permission has been granted by user
        } else{
            ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
        }
    }

    private void startCamera() {

        final ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);

        cameraProviderFuture.addListener(new Runnable() {
            @Override
            public void run() {
                try {

                    ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
                    bindPreview(cameraProvider);

                } catch (ExecutionException | InterruptedException e) {
                    // No errors need to be handled for this Future.
                    // This should never be reached.
                }
            }
        }, ContextCompat.getMainExecutor(this));
    }

    void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {

        Preview preview = new Preview.Builder()
                .build();

        CameraSelector cameraSelector = new CameraSelector.Builder()
                .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                .build();

        ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
                .build();

        ImageCapture.Builder builder = new ImageCapture.Builder();

        //Vendor-Extensions (The CameraX extensions dependency in build.gradle)
        HdrImageCaptureExtender hdrImageCaptureExtender = HdrImageCaptureExtender.create(builder);

        // Query if extension is available (optional).
        if (hdrImageCaptureExtender.isExtensionAvailable(cameraSelector)) {
            // Enable the extension if available.
            hdrImageCaptureExtender.enableExtension(cameraSelector);
        }

        final ImageCapture imageCapture = builder
                .setTargetRotation(this.getWindowManager().getDefaultDisplay().getRotation())
                .build();
preview.setSurfaceProvider(mPreviewView.createSurfaceProvider());
        Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner)this, cameraSelector, preview, imageAnalysis, imageCapture);

        

        captureImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US);
                File file = new File(getBatchDirectoryName(), mDateFormat.format(new Date())+ ".jpg");

                ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file).build();
                imageCapture.takePicture(outputFileOptions, executor, new ImageCapture.OnImageSavedCallback () {
                    @Override
                    public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                        new Handler().post(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(MainActivity.this, "Image Saved successfully", Toast.LENGTH_SHORT).show();
                            }
                        });
                    }
                    @Override
                    public void onError(@NonNull ImageCaptureException error) {
                        error.printStackTrace();
                    }
                });
            }
        });
    }

    public String getBatchDirectoryName() {

        String app_folder_path = "";
        app_folder_path = Environment.getExternalStorageDirectory().toString() + "/images";
        File dir = new File(app_folder_path);
        if (!dir.exists() && !dir.mkdirs()) {

        }

        return app_folder_path;
    }

    private boolean allPermissionsGranted(){

        for(String permission : REQUIRED_PERMISSIONS){
            if(ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED){
                return false;
            }
        }
        return true;
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        if(requestCode == REQUEST_CODE_PERMISSIONS){
            if(allPermissionsGranted()){
                startCamera();
            } else{
                Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show();
                this.finish();
            }
        }
    }
}

Credit: Developer.android.com

Image Credit: Google Images

That’s all about the simple tutorial on implementation of CameraX. If you have any thoughts or questions that you’d like to share, then please do reach out!