title: Display a rectangular bounding box (overlay) on custom Camera2 API so only the image inside the box is captured
body: <p>I'm using the following code to capture an image using the custom Camera2 API. I am a beginner and I have no idea how to display a rectangular overlay on the camera preview. I did try the solutions available here, but I can't figure out how to fit it in my code.</p>
  
  <p>I need to achieve something like this to capture A4 sheets.</p>
  
  <p><a href="https://i.stack.imgur.com/CMbiZ.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/CMbiZ.png" alt="Rectangular overlay"></a></p>
  
  <p>Only the part of the image which is in the box must be captured and displayed in the next activity.</p>
  
  <p>MainActivity</p>
  
  <pre><code>package com.example.customcamera;
  
  import android.Manifest;
  import android.content.Context;
  import android.content.Intent;
  import android.content.pm.PackageManager;
  import android.graphics.ImageFormat;
  import android.graphics.SurfaceTexture;
  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.CameraMetadata;
  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.Bundle;
  import android.os.Environment;
  import android.os.Handler;
  import android.os.HandlerThread;
  import android.support.annotation.NonNull;
  import android.support.v4.app.ActivityCompat;
  import android.support.v4.content.ContextCompat;
  import android.support.v7.app.AppCompatActivity;
  import android.util.Log;
  import android.util.Size;
  import android.util.SparseIntArray;
  import android.view.Surface;
  import android.view.TextureView;
  import android.view.View;
  import android.widget.Button;
  import android.widget.Toast;
  
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.nio.ByteBuffer;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.UUID;
  
  public class MainActivity extends AppCompatActivity {
  
      private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
      static
      {
          ORIENTATIONS.append(Surface.ROTATION_0, 90);
          ORIENTATIONS.append(Surface.ROTATION_90, 0);
          ORIENTATIONS.append(Surface.ROTATION_180, 270);
          ORIENTATIONS.append(Surface.ROTATION_270, 180);
      }
  
      TextureView textureView;
      private CameraDevice cameraDevice;
      private String cameraId;
      private Size imageDimensions;
      private CameraCaptureSession cameraCaptureSession;
      private CaptureRequest.Builder captureRequestBuilder;
      private ImageReader imageReader;
      private boolean flashSupported;
      private HandlerThread backgroundThread;
      private Handler backgroundHandler;
  
  
      CameraDevice.StateCallback stateCallback;
      File file;
      final int MY_PERMISSIONS_REQUEST_CAMERA=102;
  
  
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
  
  
          stateCallback = new CameraDevice.StateCallback() {
              @Override
              public void onOpened(@NonNull CameraDevice camera) {
                  cameraDevice = camera;
                  createCameraPreview();
              }
  
              @Override
              public void onDisconnected(@NonNull CameraDevice cameraDevice) {
                  MainActivity.this.cameraDevice.close();
              }
  
              @Override
              public void onError(@NonNull CameraDevice cameraDevice, int i) {
                  MainActivity.this.cameraDevice.close();
                  MainActivity.this.cameraDevice = null;        // Change to global
              }
          };
  
          textureView = findViewById(R.id.textureView);
          textureView.setSurfaceTextureListener(textureListener);
  
          Button btn_camera = findViewById(R.id.btn_camera);
          btn_camera.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  takePicture();
              }
          });
      }
  
      public void takePicture()
      {
          if(cameraDevice == null)
              return;
  
          CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
  
          try
          {
              CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraDevice.getId());
              Size[] jpegSizes = null;
  
              if(cameraCharacteristics == null)
                  jpegSizes = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(ImageFormat.JPEG);
  
              // Capture image with custom size
              int width = 640;
              int height = 480;
  
              if(jpegSizes != null &amp;&amp; jpegSizes.length &gt; 0)
              {
                  width = jpegSizes[0].getWidth();
                  height = jpegSizes[0].getHeight();
              }
  
              imageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
              final List&lt;Surface&gt; outputSurface = new ArrayList&lt;&gt;(2);
              outputSurface.add(imageReader.getSurface());
              outputSurface.add(new Surface(textureView.getSurfaceTexture()));
  
              final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
              captureBuilder.addTarget(imageReader.getSurface());
              captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
  
              // Check orientation base on device
              int rotation = getWindowManager().getDefaultDisplay().getRotation();
              captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
  
              ImageReader.OnImageAvailableListener readerListener = new ImageReader.OnImageAvailableListener() {
                  @Override
                  public void onImageAvailable(ImageReader imageReader) {
  
                      file = new File(Environment.getExternalStorageDirectory() + "/" + UUID.randomUUID().toString() + ".jpg");
                      Image image = null;
  
                      try
                      {
                          image = imageReader.acquireLatestImage();
                          ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                          buffer.rewind();
                          byte[] bytes = new byte[buffer.capacity()];
                          buffer.get(bytes);
                          save(bytes);
  
                          startActivity(new Intent(MainActivity.this, ImageDisplay.class).putExtra("FILE", file.getPath()));
                      }
  
                      finally {
                          if(image != null)
                              image.close();
                      }
                  }
  
                  public void save(byte[] bytes)
                  {
                      OutputStream outputStream = null;
  
                      try
                      {
                          outputStream = new FileOutputStream(file);
                          outputStream.write(bytes);
                      } catch (FileNotFoundException e) {
                          e.printStackTrace();
                      } catch (IOException e) {
                          e.printStackTrace();
                      } finally {
                          if(outputStream == null) {
                              try {
                                  outputStream.close();
                              } catch (IOException e) {
                                  e.printStackTrace();
                              }
                          }
                      }
                  }
              };
  
              imageReader.setOnImageAvailableListener(readerListener, backgroundHandler);
  
              final CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
                  @Override
                  public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                      super.onCaptureCompleted(session, request, result);
                      Toast.makeText(MainActivity.this, "Saved", Toast.LENGTH_SHORT).show();
                      createCameraPreview();
                  }
              };
  
              cameraDevice.createCaptureSession(outputSurface, new CameraCaptureSession.StateCallback() {
                  @Override
                  public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                      try {
                          cameraCaptureSession.capture(captureBuilder.build(), captureCallback, backgroundHandler);
                      } catch (CameraAccessException e) {
                          e.printStackTrace();
                      }
                  }
  
                  @Override
                  public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
  
                  }
              }, backgroundHandler);
          }
          catch (CameraAccessException e) {
              e.printStackTrace();
          }
      }
  
      private void createCameraPreview() {
  
          SurfaceTexture texture = textureView.getSurfaceTexture();
          assert texture != null;
  
          texture.setDefaultBufferSize(imageDimensions.getWidth(), imageDimensions.getHeight());
          Surface surface = new Surface(texture);
          try {
              captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
              captureRequestBuilder.addTarget(surface);
  
              cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
                  @Override
                  public void onConfigured(@NonNull CameraCaptureSession session) {
                      if(cameraDevice == null)
                          return;
  
                      cameraCaptureSession = session;
                      updatePreview();
                  }
  
                  @Override
                  public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                      Toast.makeText(MainActivity.this, "Changed", Toast.LENGTH_SHORT).show();
                  }
              }, null);
          } catch (CameraAccessException e) {
              e.printStackTrace();
          }
      }
  
      private void updatePreview() {
          if(cameraDevice == null)
              Toast.makeText(MainActivity.this, "Error!", Toast.LENGTH_SHORT).show();
  
          captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
          try {
              cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler);
          } catch (CameraAccessException e) {
              e.printStackTrace();
          }
  
      }
  
      private void openCamera()
      {
          CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
          try
          {
              cameraId = cameraManager.getCameraIdList()[0];
              CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
              StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
              assert map != null;
              imageDimensions = map.getOutputSizes(SurfaceTexture.class)[0];
  
              if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                  // Permission to Camera is not granted, request for permission
                  ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA,
                                  Manifest.permission.WRITE_EXTERNAL_STORAGE},  MY_PERMISSIONS_REQUEST_CAMERA);
                  return;
              }
  
              cameraManager.openCamera(cameraId, stateCallback, null);
          }
          catch (CameraAccessException e) {
              e.printStackTrace();
          }
      }
  
      TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
          @Override
          public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
              openCamera();
          }
  
          @Override
          public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
  
          }
  
          @Override
          public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
              Log.e("Surfacetexturedestroyed", "called");
              if(cameraDevice != null)
              {
                  Log.e("Camera not null", "make null");
                  cameraDevice.close();
                  cameraDevice = null;
              }
              return false;
          }
  
          @Override
          public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
  
          }
      };
  
      @Override
      protected void onResume() {
          super.onResume();
  
          startBackgroundThread();
  
          if(textureView.isAvailable())
              openCamera();
          else
              textureView.setSurfaceTextureListener(textureListener);
      }
  
      @Override
      protected void onPause() {
          stopBackgroundThread();
          super.onPause();
      }
  
      private void stopBackgroundThread() {
          backgroundThread.quitSafely();
          try {
              backgroundThread.join();
              backgroundThread = null;
              backgroundHandler = null;
          }
          catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
  
      private void startBackgroundThread() {
          backgroundThread = new HandlerThread("Camera Background");
          backgroundThread.start();
          backgroundHandler = new Handler(backgroundThread.getLooper());
      }
  
      /**
       * Handler for handling the user event after requesting permission.
       */
      @Override
      public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)
      {
          switch (requestCode)
          {
              case 102:     //MY_PERMISSIONS_REQUEST_CAMERA=102   Use Camera
  
                  // If request is cancelled, the result arrays are empty.
                  if (grantResults.length &gt; 0  &amp;&amp; grantResults[0] == PackageManager.PERMISSION_GRANTED)
                  {
                      // permission was granted, yay!
                      openCamera();
                  }
                  else
                  {
                      // permission denied, boo!
                      Toast.makeText(this, "Permission to use device Camera denied! Cannot proceed ahead!", Toast.LENGTH_SHORT).show();
                      finish();
                  }
                  break;
  
              default: Toast.makeText(this, "Failed to handle permissions response!", Toast.LENGTH_SHORT).show();
          }
      }
  
  }
  </code></pre>
  
  <p>activity_main.xml</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      tools:context=".MainActivity"&gt;
  
      &lt;TextureView
          android:layout_width="wrap_content"
          android:layout_height="match_parent"
          android:id="@+id/textureView"
          android:layout_alignParentTop="true"/&gt;
  
      &lt;Button
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="Take Picture"
          android:textSize="30sp"
          android:id="@+id/btn_camera"
          android:layout_alignParentBottom="true"
          android:layout_margin="8dp"/&gt;
  
  &lt;/RelativeLayout&gt;
  </code></pre>
  
  <p>ImageDisplay Activity</p>
  
  <pre><code>package com.example.customcamera;
  
  import android.graphics.Bitmap;
  import android.graphics.BitmapFactory;
  import android.graphics.Path;
  import android.media.Image;
  import android.support.v7.app.AppCompatActivity;
  import android.os.Bundle;
  import android.widget.ImageView;
  
  import java.io.File;
  
  public class ImageDisplay extends AppCompatActivity {
  
      ImageView imageView;
  
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.image_display);
  
          imageView = findViewById(R.id.imageView);
  
          String imgFile = getIntent().getStringExtra("FILE");
  
          Bitmap bitmap = BitmapFactory.decodeFile(imgFile);
          imageView.setImageBitmap(bitmap);
      }
  }
  
  </code></pre>
  
  <p>image_display.xml</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      tools:context=".ImageDisplay"&gt;
  
      &lt;ImageView
          android:id="@+id/imageView"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content" /&gt;
  &lt;/RelativeLayout&gt;
  </code></pre>