the previous time

In previous post we created test, which gets desiredcameraId, then we opened CameraDevice for that cameraId. In brief we write this code:

                package com.example.myapplication;


                import android.Manifest;
                import android.content.Context;
                import android.hardware.camera2.CameraAccessException;
                import android.hardware.camera2.CameraCharacteristics;
                import android.hardware.camera2.CameraDevice;
                import android.hardware.camera2.CameraManager;
                import android.os.Handler;
                import android.os.HandlerThread;
                import android.util.Log;

                import androidx.annotation.NonNull;
                import androidx.test.InstrumentationRegistry;
                import androidx.test.filters.SmallTest;
                import androidx.test.rule.GrantPermissionRule;
                import androidx.test.runner.AndroidJUnit4;

                import org.junit.Rule;
                import org.junit.Test;
                import org.junit.runner.RunWith;

                @RunWith(AndroidJUnit4.class)
                @SmallTest
                public class ExampleInstrumentedTest {


                    @Rule
                    public GrantPermissionRule permissionRule = GrantPermissionRule.grant(Manifest.permission.CAMERA);

                    // create variable for holding device
                    private CameraDevice cameraDevice;

                    // create state object, to pass to open method. Implements required methods
                    private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
                        @Override
                        public void onOpened(@NonNull CameraDevice camera) {
                            // this method is most important for us. Assign opened device to our variable defined above
                            cameraDevice = camera;
                            Log.d("ExampleInstrumentedTest", "CameraDevice.StateCallback::onOpened");
                        }

                        @Override
                        public void onDisconnected(@NonNull CameraDevice camera) {
                            camera.close();
                        }

                        @Override
                        public void onError(@NonNull CameraDevice camera, int error) {
                            camera.close();
                        }
                    };


                    @Test
                    public void test1() throws CameraAccessException {
                        // get context (remember to use getTargetContext())
                        Context context = InstrumentationRegistry.getTargetContext();
                        // get camera manager
                        CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
                        // iterate over all device's cameras
                        String cameraId = null;
                        for (String camId : manager.getCameraIdList()) {
                            // and choose apropriete one, based on camera characteristics
                            CameraCharacteristics characteristics = manager.getCameraCharacteristics(camId);

                            Integer LENS_FACING = characteristics.get(CameraCharacteristics.LENS_FACING);
                            if (LENS_FACING != null && LENS_FACING == CameraCharacteristics.LENS_FACING_BACK) {
                                cameraId = camId;
                                break;
                            }
                        }

                        HandlerThread mBackgroundThread = new HandlerThread("CameraThread");
                        mBackgroundThread.start();
                        Handler backgroundHandler = new Handler(mBackgroundThread.getLooper());

                        if (cameraId != null) {
                            manager.openCamera(cameraId, mStateCallback, backgroundHandler);
                        }
                    }
                }
            

To capture single photo we need:

In this example we will capture image and save it to jpeg file.

(4) Creating OnImageAvailableListener

As title said we create listener, which is called, when Image from camera is available. That's all. We need to save it to file. More detailed listener gets ImageReader, which can get latest image. Then this image can be easly saved into file. After that, all elements should be closed: Reader, Image and File.

                // create ImageAvailableListener
                ImageReader.OnImageAvailableListener imageAvailableListener = new ImageReader.OnImageAvailableListener() {
                    // only one methon to implement
                    @Override
                    public void onImageAvailable(final ImageReader reader) {

                        // read image from ImageReader
                        final Image image = reader.acquireLatestImage();

                        // save file in background thread, to don't block UI/Camera
                        backgroundHandler.post(new Runnable() {
                            // in anonymous class impelemtn run method
                            @Override
                            public void run() {
                                //get content
                                ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                                byte[] bytes = new byte[buffer.remaining()];
                                buffer.get(bytes);
                                FileOutputStream output = null;
                                try {
                                    File file = getFile(image);
                                    output = new FileOutputStream(file);
                                    // save content to file
                                    output.write(bytes);
                                } catch (IOException e) {
                                    e.printStackTrace();
                                } finally {
                                    // close image
                                    image.close();
                                    if (null != output) {
                                        try {
                                            output.close();
                                        } catch (IOException e) {
                                            e.printStackTrace();
                                        }
                                    }
                                }
                                // close reader
                                reader.close();
                            }

                            // get file
                            private File getFile(Image image) {
                                long timestamp = image.getTimestamp();
                                File externalFileDir = Environment.getExternalStorageDirectory();
                                String folderPath = String.format("%s/%s/%s/", externalFileDir, Environment.DIRECTORY_DCIM, "MyApplication");
                                File folder = new File(folderPath);
                                if (!folder.exists()) {
                                    boolean success = folder.mkdirs();
                                }
                                File file = new File(String.format("%s/%s.%s", folderPath, timestamp, "jpg"));
                                Log.d("ExampleInstrumentedTest", "OnImageAvailableListener:save: " + file.getAbsolutePath());
                                return file;
                            }
                        });
                    }
                };
            

Anonyous runner class can be refactored to inner class. You can name it for example: TestImageSaver

(3) get largest jpeg Image Size

                CameraCharacteristics cameraCharacteristics = manager.getCameraCharacteristics(cameraId);
                StreamConfigurationMap configurationMap = cameraCharacteristics.get(
                        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                Size[] jpegOutputSized = configurationMap.getOutputSizes(ImageFormat.JPEG);
                Size largestJpegSize = Collections.max(Arrays.asList(jpegOutputSized), new Comparator() {
                    @Override
                    public int compare(Size o1, Size o2) {
                        return Long.signum((long) o1.getWidth() * o1.getHeight() -
                                (long) o2.getWidth() * o2.getHeight());
                    }
                });
            

(2) Create ImageReader

                ImageReader imageReader = ImageReader.newInstance(largestJpeg.getWidth(), largestJpeg.getHeight(), ImageFormat.JPEG, 5);
                imageReader.setOnImageAvailableListener(imageAvailableListener, backgroundHandler);
            

(5) Create CameraCaptureSession.StateCallback object and get session object

session object should be a test class' field and session state callback as well. Inside state callback, we assign configured session, to field.

                // this code is part of class definition

                // session object
                private CameraCaptureSession cameraCaptureSession;

                // session state callback
                private CameraCaptureSession.StateCallback sessionStateCallback = new CameraCaptureSession.StateCallback() {
                    @Override
                    public void onConfigured(@NonNull CameraCaptureSession session) {
                        cameraCaptureSession = session;
                        Log.d(TAG, "CameraCaptureSession.StateCallback::onConfigured");
                    }

                    @Override
                    public void onConfigureFailed(@NonNull CameraCaptureSession session) {

                    }
                };
            
                // this code is part of test method
                cameraDevice.createCaptureSession(Collections.singletonList(imageReader.getSurface()), sessionStateCallback, backgroundHandler);
            

(6) Create CaptureRequest object

                // create RequestBuilder
                CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

                // set surface in builder
                requestBuilder.addTarget(imageReader.getSurface());

                // build a single request - it will be passed to capture method
                CaptureRequest request = requestBuilder.build();
            

(7) Create CameraCaptureSession.CaptureCallback object

                // may be part of class definition (class field)
                CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
                    @Override
                    public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
                                                 long timestamp, long frameNumber) {
                        Log.d("ExampleInstrumentedTest", "CaptureCallback::onCaptureStarted");
                    }

                    @Override
                    public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
                                                   @NonNull TotalCaptureResult result) {
                        Log.d("ExampleInstrumentedTest", "CaptureCallback::onCaptureCompleted");
                    }
                };
            

capture picture

It may be not obvious, but when camera code is invoked with any handler object. Then this method is not run explicite in that moment. Part of this method is posted to a handler, and is invoked some kind "in background". In other words we should know how multithreading is working in Java. To solve this problem, just run capture method in the same thread handler, as other camera methods was invoked. To do this, google for: "java handler post" or look:

                backgroundHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Log.d("ExampleInstrumentedTest", "cameraCaptureSession.capture");
                            cameraCaptureSession.capture(request, captureCallback, backgroundHandler);
                        } catch (CameraAccessException e) {
                            Assert.fail("CameraAccessException during creating request!");
                            e.printStackTrace();
                        }
                    }
                });
                // at the end, add waiting, to finish all thread tasks
                try {
                    // explicit sleep, to finish capturing image
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            

TL;DR

                package com.example.myapplication;


                import android.Manifest;
                import android.content.Context;
                import android.graphics.ImageFormat;
                import android.hardware.camera2.CameraAccessException;
                import android.hardware.camera2.CameraCaptureSession;
                import android.hardware.camera2.CameraCharacteristics;
                import android.hardware.camera2.CameraDevice;
                import android.hardware.camera2.CameraManager;
                import android.hardware.camera2.CaptureRequest;
                import android.hardware.camera2.TotalCaptureResult;
                import android.hardware.camera2.params.StreamConfigurationMap;
                import android.media.Image;
                import android.media.ImageReader;
                import android.os.Environment;
                import android.os.Handler;
                import android.os.HandlerThread;
                import android.util.Log;
                import android.util.Size;

                import androidx.annotation.NonNull;
                import androidx.test.InstrumentationRegistry;
                import androidx.test.filters.SmallTest;
                import androidx.test.rule.GrantPermissionRule;
                import androidx.test.runner.AndroidJUnit4;

                import junit.framework.Assert;

                import org.junit.Rule;
                import org.junit.Test;
                import org.junit.runner.RunWith;

                import java.io.File;
                import java.io.FileOutputStream;
                import java.io.IOException;
                import java.nio.ByteBuffer;
                import java.util.Arrays;
                import java.util.Collections;
                import java.util.Comparator;

                @RunWith(AndroidJUnit4.class)
                @SmallTest
                public class ExampleInstrumentedTest {


                    @Rule
                    public GrantPermissionRule permissionRule = GrantPermissionRule.grant(Manifest.permission.CAMERA);

                    // create variable for holding device
                    private CameraDevice cameraDevice;

                    // create state object, to pass to open method. Implements required methods
                    private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
                        @Override
                        public void onOpened(@NonNull CameraDevice camera) {
                            // this method is most important for us. Assign opened device to our variable defined above
                            cameraDevice = camera;
                            Log.d("ExampleInstrumentedTest", "CameraDevice.StateCallback::onOpened");
                        }

                        @Override
                        public void onDisconnected(@NonNull CameraDevice camera) {
                            camera.close();
                        }

                        @Override
                        public void onError(@NonNull CameraDevice camera, int error) {
                            camera.close();
                        }
                    };


                    private CameraCaptureSession cameraCaptureSession;
                    private CameraCaptureSession.StateCallback sessionStateCallback = new CameraCaptureSession.StateCallback() {
                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession session) {
                            cameraCaptureSession = session;
                            Log.d("ExampleInstrumentedTest", "CameraCaptureSession.StateCallback::onConfigured");
                        }

                        @Override
                        public void onConfigureFailed(@android.support.annotation.NonNull CameraCaptureSession session) {

                        }
                    };


                    private CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
                        @Override
                        public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
                                                     long timestamp, long frameNumber) {
                            Log.d("ExampleInstrumentedTest", "CaptureCallback::onCaptureStarted");
                        }

                        @Override
                        public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
                                                       @NonNull TotalCaptureResult result) {
                            Log.d("ExampleInstrumentedTest", "CaptureCallback::onCaptureCompleted");
                        }
                    };


                    @Test
                    public void test1() throws CameraAccessException {
                        // get context (remember to use getTargetContext())
                        final Context context = InstrumentationRegistry.getTargetContext();
                        // get camera manager
                        CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
                        // iterate over all device's cameras
                        String cameraId = null;
                        for (String camId : manager.getCameraIdList()) {
                            // and choose apropriete one, based on camera characteristics
                            CameraCharacteristics characteristics = manager.getCameraCharacteristics(camId);

                            Integer LENS_FACING = characteristics.get(CameraCharacteristics.LENS_FACING);
                            if (LENS_FACING != null && LENS_FACING == CameraCharacteristics.LENS_FACING_BACK) {
                                cameraId = camId;
                                break;
                            }
                        }

                        CameraCharacteristics cameraCharacteristics = manager.getCameraCharacteristics(cameraId);
                        StreamConfigurationMap configurationMap = cameraCharacteristics.get(
                                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                        Size[] jpegOutputSized = configurationMap.getOutputSizes(ImageFormat.JPEG);
                        Size largestJpegSize = Collections.max(Arrays.asList(jpegOutputSized), new Comparator() {
                            @Override
                            public int compare(Size o1, Size o2) {
                                return Long.signum((long) o1.getWidth() * o1.getHeight() -
                                        (long) o2.getWidth() * o2.getHeight());
                            }
                        });

                        HandlerThread mBackgroundThread = new HandlerThread("CameraThread");
                        mBackgroundThread.start();
                        final Handler backgroundHandler = new Handler(mBackgroundThread.getLooper());

                        if (cameraId != null) {
                            manager.openCamera(cameraId, mStateCallback, backgroundHandler);
                        }

                        ImageReader.OnImageAvailableListener imageAvailableListener = new ImageReader.OnImageAvailableListener() {
                            @Override
                            public void onImageAvailable(final ImageReader reader) {
                                final Image image = reader.acquireLatestImage();
                                backgroundHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                                        byte[] bytes = new byte[buffer.remaining()];
                                        buffer.get(bytes);
                                        FileOutputStream output = null;
                                        try {
                                            File file = getFile(image);
                                            output = new FileOutputStream(file);
                                            output.write(bytes);
                                        } catch (IOException e) {
                                            e.printStackTrace();
                                        } finally {
                                            image.close();
                                            if (null != output) {
                                                try {
                                                    output.close();
                                                } catch (IOException e) {
                                                    e.printStackTrace();
                                                }
                                            }
                                        }
                                        reader.close();
                                    }

                                    private File getFile(Image image) {
                                        long timestamp = image.getTimestamp();
                                        File externalFileDir = Environment.getExternalStorageDirectory();
                                        String folderPath = String.format("%s/%s/%s/", externalFileDir, Environment.DIRECTORY_DCIM, "MyApplication");
                                        File folder = new File(folderPath);
                                        if (!folder.exists()) {
                                            boolean success = folder.mkdirs();
                                        }
                                        File file = new File(String.format("%s/%s.%s", folderPath, timestamp, "jpg"));
                                        Log.d("ExampleInstrumentedTest", "OnImageAvailableListener:save: " + file.getAbsolutePath());
                                        return file;
                                    }
                                });
                            }
                        };

                        final ImageReader imageReader = ImageReader.newInstance(largestJpegSize.getWidth(), largestJpegSize.getHeight(), ImageFormat.JPEG, 5);
                        imageReader.setOnImageAvailableListener(imageAvailableListener, backgroundHandler);


                        cameraDevice.createCaptureSession(Collections.singletonList(imageReader.getSurface()), sessionStateCallback, backgroundHandler);

                        Log.d("ExampleInstrumentedTest", "createCaptureRequest");
                        CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

                        Log.d("ExampleInstrumentedTest", "addTarget");
                        requestBuilder.addTarget(imageReader.getSurface());

                        Log.d("ExampleInstrumentedTest", "requestBuilder.build");
                        final CaptureRequest request = requestBuilder.build();

                        backgroundHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    Log.d("ExampleInstrumentedTest", "cameraCaptureSession.capture");
                                    cameraCaptureSession.capture(request, captureCallback, backgroundHandler);
                                } catch (CameraAccessException e) {
                                    Assert.fail("CameraAccessException during creating request!");
                                    e.printStackTrace();
                                }
                            }
                        });

                        try {
                            // explicit sleep, to finish capturing image
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                    }
                }