Camera 2 API

1) Create Textureview and TextureView.SurfaceTextureListener

// texture view
private TextureView mTextureView;
// texture view listener
private TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        Log.i("texture view", "now available");
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }
};

2) Create CameraDevice and its listener CameraDevice.StateCallback

private CameraDevice mCameraDevice;
private CameraDevice.StateCallback mCameraDeviceStateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(@androidx.annotation.NonNull CameraDevice camera) {
        mCameraDevice = camera;
    }

    @Override
    public void onDisconnected(@androidx.annotation.NonNull CameraDevice camera) {
        camera.close();
        mCameraDevice = null;
    }

    @Override
    public void onError(@androidx.annotation.NonNull CameraDevice camera, int error) {
        camera.close();
        mCameraDevice = null;
    }
};

3) Find the camera that matches the required characteristics (say front facing camera)

private String mCameraId;

private void setUpCamera(int width, int height){
    // get camera manager
    // loop through the list of cameras
    // find the camera that matches the required characteristics (say, front facing)
    CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    try {
        for(String cameraId : cameraManager.getCameraIdList()){
            CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
            // if the camera is front facing, skip it
            if(cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)
                continue;
            mCameraId = cameraId;
            return;
        }
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

4) Add setUpCamera method in the SurfaceTexturListener class under onSurfaceTextureAvailable method

private TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        Log.i("texture view", "now available");
        setUpCamera(width, height);
    }
   ...

5) We dont want to run the camera activity in the UI thread. So we create a background handler thread

// we dont want the camera app to affect the UI thread
private HandlerThread mBackgroundHandlerThread;
private Handler mBackgroundHandler;

private void startBackgroundThread(){
    // pass some string
    mBackgroundHandlerThread = new HandlerThread("cameraapi");
    mBackgroundHandlerThread.start();
    mBackgroundHandler = new Handler(mBackgroundHandlerThread.getLooper());
}

private void stopBackgroundThread(){
    mBackgroundHandlerThread.quitSafely();
    try {
        mBackgroundHandlerThread.join();
        mBackgroundHandlerThread = null;
        mBackgroundHandler = null;
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

5) Implement onPause and onResume. This is important to manage memory.

On resume : start background thread and set up camera.

On Pause : close camera and stop background thread

@Override
protected void onPause() {
    mCameraDevice.close();
    mCameraDevice = null;
    stopBackgroundThread();
    super.onPause();
}

// called when app resumes after pause
@Override
protected void onResume() {
    super.onResume();
    startBackgroundThread();
    if(!mTextureView.isAvailable()){
        mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
    }else{
        setUpCamera(mTextureView.getWidth(), mTextureView.getHeight());
    }
}

6) connect camera : Call openCamera from CameraManager and pass cam id, cam callback and handler

private void connectCamera() {
    CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    try {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
            cameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, mBackgroundHandler);
        }
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

call connectCamera after SetUp camera call

protected void onResume() {
            ...
            if(!mTextureView.isAvailable()){
                mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
            }else{
                setUpCamera(mTextureView.getWidth(), mTextureView.getHeight());
                connectCamera();
            }
        }

and also under surface texture listener

public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            Log.i("texture view", "now available");
            setUpCamera(width, height);
            connectCamera();
        }

7) Compute buffer size. It has to be among one of the resolutions obtained from camera characertestics that is close to the aspect ratio of the texture view size. This calculation is implemeted in CalculateTextureSize.java. For now, we ll use these calculations as a black box

In setUpCamera method

...
CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
...
// Compute the size for textureView buffer
int deviceOrientation = getWindowManager().getDefaultDisplay().getRotation();
CalculateTextureSize calcSize = new CalculateTextureSize(cameraCharacteristics,
                                                         width,
                                                         height,
                                                         deviceOrientation);

StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
mPreviewSize = calcSize.chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class));
...

7) Capture preview : Bind camera device with surface view

private CaptureRequest.Builder mCaptureRequestBuilder;

private void startPreview(){
    SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
    // computing the buffer size is a bit complicated. Should eventually get this value for oppo a3s
    // surfaceTexture.setDefaultBufferSize(1280,720);
    surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
    Surface previewSurface = new Surface(surfaceTexture);

    try {
        mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        mCaptureRequestBuilder.addTarget(previewSurface);

        mCameraDevice.createCaptureSession(Arrays.asList(previewSurface),
                new CameraCaptureSession.StateCallback() {
                    @Override
                    public void onConfigured(CameraCaptureSession session) {
                        try {
                          session.setRepeatingRequest(mCaptureRequestBuilder.build(),null,mBackgroundHandler);
                        } catch (CameraAccessException e) {
                          e.printStackTrace();
                        }
                    }
                    @Override
                    public void onConfigureFailed(CameraCaptureSession session) {
                        Toast.makeText(getApplicationContext(),"failed",Toast.LENGTH_SHORT).show();
                    }
                }, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

8) call startPreview when camera opened is listened by CameraDevice.Statecallback

private CameraDevice.StateCallback mCameraDeviceStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice camera) {
            mCameraDevice = camera;
            startPreview();
        }