content: /*
   * Copyright (C) 2018 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package androidx.demo.mycamera;
  
  import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
  import static android.content.pm.PackageManager.PERMISSION_GRANTED;
  
  import android.Manifest;
  import android.app.Activity;
  import android.content.Context;
  import android.content.ContextWrapper;
  import android.content.pm.PackageManager;
  import android.graphics.ImageFormat;
  import android.graphics.Matrix;
  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.Environment;
  import android.os.Handler;
  import android.os.HandlerThread;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.util.Size;
  import android.util.SparseIntArray;
  import android.view.Surface;
  import android.view.TextureView;
  import android.widget.Toast;
  
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  import androidx.constraintlayout.motion.widget.Debug;
  import androidx.core.app.ActivityCompat;
  import androidx.core.content.ContextCompat;
  
  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;
  
  public class CameraPreview extends TextureView implements ActivityCompat.OnRequestPermissionsResultCallback {
      Context mContext;
      Cam mCam;
  
      public CameraPreview(Context context) {
          super(context);
          init(context);
      }
  
      public CameraPreview(Context context, @Nullable AttributeSet attrs) {
          super(context, attrs);
          init(context);
      }
  
      public CameraPreview(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
          init(context);
      }
  
      private static Activity getActivity(Context context) {
  
          while (context instanceof ContextWrapper) {
              if (context instanceof Activity) {
                  return (Activity) context;
              }
              context = ((ContextWrapper) context).getBaseContext();
          }
          return null;
      }
  
      @Override
      protected void onSizeChanged(int w, int h, int oldw, int oldh) {
          super.onSizeChanged(w, h, oldw, oldh);
          if (w > h) {
              mCam.orientation(1);
          } else mCam.orientation(0);
      }
  
      private void init(Context context) {
          mContext = context;
          mCam = new CameraPreview.Cam(getActivity(context), this);
      }
  
      public void on() {
          mCam.on();
      }
  
      public void pause() {
          mCam.pause();
          Log.e("CameraPreview", Debug.getLoc() + "");
      }
  
      public void resume() {
          mCam.resume();
          Log.e("CameraPreview", Debug.getLoc() + "");
      }
  
      public void takePicture() {
          mCam.takePicture();
      }
  
      @Override
      public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
          mCam.onRequestPermissionsResult(requestCode, permissions, grantResults);
      }
  
      static class Cam {
          static final int FINE_LOCATION_PERMISSION_REQUEST = 2345;
          private static final int REQUEST_CAMERA_PERMISSION = 5432;
          private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
          private static final String TAG = "Cam";
  
          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);
          }
  
          private final Activity mActivity;
          protected CameraDevice cameraDevice;
          protected CameraCaptureSession cameraCaptureSessions;
          protected CaptureRequest.Builder captureRequestBuilder;
          Runnable onPermissionGranted;
          TextureView mTextureView;
          private String cameraId;
          private Size imageDimension;
          TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
              @Override
              public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                  openCamera();
              }
  
              @Override
              public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
              }
  
              @Override
              public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                  return false;
              }
  
              @Override
              public void onSurfaceTextureUpdated(SurfaceTexture surface) {
              }
          };
          private ImageReader imageReader;
          private Handler mBackgroundHandler;
          private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
              @Override
              public void onOpened(CameraDevice camera) {
                  Log.e(TAG, Debug.getLoc() + "  onOpened");
                  cameraDevice = camera;
                  createCameraPreview();
              }
  
              @Override
              public void onDisconnected(CameraDevice camera) {
                  cameraDevice.close();
              }
  
              @Override
              public void onError(CameraDevice camera, int error) {
                  cameraDevice.close();
                  cameraDevice = null;
              }
          };
          final CameraCaptureSession.CaptureCallback captureCallbackListener = new CameraCaptureSession.CaptureCallback() {
              @Override
              public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
                  super.onCaptureCompleted(session, request, result);
                  Toast.makeText(mActivity, "Saved:" , Toast.LENGTH_SHORT).show();
                  createCameraPreview();
              }
          };
          private HandlerThread mBackgroundThread;
  
          Cam(Activity activity, TextureView preview) {
              mActivity = activity;
              mTextureView = preview;
          }
  
          public void on() {
              getPermissionAndRun(() -> mTextureView.setSurfaceTextureListener(textureListener));
          }
  
          void getPermissionAndRun(Runnable runnable) {
              if (ContextCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA)
                      == PackageManager.PERMISSION_DENIED) {
                  onPermissionGranted = runnable;
                  ActivityCompat.requestPermissions(mActivity,
                          new String[]{Manifest.permission.CAMERA},
                          FINE_LOCATION_PERMISSION_REQUEST);
              } else {
                  runnable.run();
              }
          }
  
          public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
              if (requestCode == FINE_LOCATION_PERMISSION_REQUEST) {
                  if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
                      Toast.makeText(mActivity, "Sorry no permission", Toast.LENGTH_LONG).show();
                      return;
                  }
                  onPermissionGranted.run();
              }
          }
  
          protected void startBackgroundThread() {
              mBackgroundThread = new HandlerThread("Camera");
              mBackgroundThread.start();
              mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
          }
  
          protected void stopBackgroundThread() {
              mBackgroundThread.quitSafely();
              try {
                  mBackgroundThread.join();
                  mBackgroundThread = null;
                  mBackgroundHandler = null;
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
  
          protected void takePicture() {
              if (null == cameraDevice) {
                  Log.e(TAG, Debug.getLoc() + "cameraDevice is null");
                  return;
              }
              CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
              try {
                  CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraDevice.getId());
                  Size[] jpegSizes = null;
                  if (characteristics != null) {
                      jpegSizes = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(ImageFormat.JPEG);
                  }
                  int width = 640;
                  int height = 480;
                  if (jpegSizes != null && 0 < jpegSizes.length) {
                      width = jpegSizes[0].getWidth();
                      height = jpegSizes[0].getHeight();
                  }
                  ImageReader reader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
                  List<Surface> outputSurfaces = new ArrayList<Surface>(2);
                  outputSurfaces.add(reader.getSurface());
                  outputSurfaces.add(new Surface(mTextureView.getSurfaceTexture()));
                  final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                  captureBuilder.addTarget(reader.getSurface());
                  captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
                  int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
                  captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
                  final File file = new File(Environment.getExternalStorageDirectory() + "/pic.jpg");
                  ImageReader.OnImageAvailableListener readerListener = new ImageReader.OnImageAvailableListener() {
                      @Override
                      public void onImageAvailable(ImageReader reader) {
                          Image image = null;
                          try {
                              image = reader.acquireLatestImage();
                              ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                              byte[] bytes = new byte[buffer.capacity()];
                              buffer.get(bytes);
                              save(bytes);
                          } catch (FileNotFoundException e) {
                              e.printStackTrace();
                          } catch (IOException e) {
                              e.printStackTrace();
                          } finally {
                              if (image != null) {
                                  image.close();
                              }
                          }
                      }
  
                      private void save(byte[] bytes) throws IOException {
                          OutputStream output = null;
                          try {
                              output = new FileOutputStream(file);
                              output.write(bytes);
                          } finally {
                              if (null != output) {
                                  output.close();
                              }
                          }
                      }
                  };
                  reader.setOnImageAvailableListener(readerListener, mBackgroundHandler);
                  final CameraCaptureSession.CaptureCallback captureListener = new CameraCaptureSession.CaptureCallback() {
                      @Override
                      public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
                          super.onCaptureCompleted(session, request, result);
                          Toast.makeText(mActivity, "Saved:" + file, Toast.LENGTH_SHORT).show();
                          createCameraPreview();
                      }
                  };
                  cameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
                      @Override
                      public void onConfigured(CameraCaptureSession session) {
                          try {
                              session.capture(captureBuilder.build(), captureListener, mBackgroundHandler);
                          } catch (CameraAccessException e) {
                              e.printStackTrace();
                          }
                      }
  
                      @Override
                      public void onConfigureFailed(CameraCaptureSession session) {
                      }
                  }, mBackgroundHandler);
              } catch (CameraAccessException e) {
                  e.printStackTrace();
              }
          }
  
          public void orientation(int ori) {
              if (ori == 1) {
                  Matrix matrix = new Matrix();
                  Log.v(TAG, Debug.getLoc() + " rotate");
                  matrix.postRotate(-90, mTextureView.getWidth() / 2f, mTextureView.getHeight() / 2f);
                  matrix.postScale(1.7f, 1.7f, mTextureView.getWidth() / 2f, mTextureView.getHeight() / 2f);
  
                  mTextureView.setTransform(matrix);
              } else {
                  mTextureView.setTransform(null);
              }
          }
  
          protected void createCameraPreview() {
              try {
                  Log.v(TAG, Debug.getLoc() + " createCameraPreview");
                  SurfaceTexture texture = mTextureView.getSurfaceTexture();
                  assert texture != null;
  
                  texture.setDefaultBufferSize(imageDimension.getWidth(), imageDimension.getHeight());
                  Surface surface = new Surface(texture);
                  captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
  
                  captureRequestBuilder.addTarget(surface);
                  cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
                      @Override
                      public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                          if (null == cameraDevice) {
                              return;
                          }
                          cameraCaptureSessions = cameraCaptureSession;
                          updatePreview();
                      }
  
                      @Override
                      public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                          Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT).show();
                      }
                  }, null);
              } catch (CameraAccessException e) {
                  e.printStackTrace();
              }
          }
  
          private void openCamera() {
              CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
              try {
                  cameraId = manager.getCameraIdList()[0];
                  CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
                  StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                  assert map != null;
                  imageDimension = map.getOutputSizes(SurfaceTexture.class)[0];
                  if (ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != PERMISSION_GRANTED
                          && ActivityCompat.checkSelfPermission(mActivity, WRITE_EXTERNAL_STORAGE) != PERMISSION_GRANTED) {
                      ActivityCompat.requestPermissions(mActivity, new String[]{Manifest.permission.CAMERA, WRITE_EXTERNAL_STORAGE}, REQUEST_CAMERA_PERMISSION);
                      return;
                  }
                  manager.openCamera(cameraId, stateCallback, null);
              } catch (CameraAccessException e) {
                  e.printStackTrace();
              }
              Log.e(TAG, Debug.getLoc() + "openCamera X");
          }
  
          protected void updatePreview() {
              if (null == cameraDevice) {
                  Log.e(TAG, Debug.getLoc() + "updatePreview error, return");
              }
              captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
              try {
                  cameraCaptureSessions.setRepeatingRequest(captureRequestBuilder.build(), null, mBackgroundHandler);
              } catch (CameraAccessException e) {
                  e.printStackTrace();
              }
          }
  
          private void closeCamera() {
              if (null != cameraDevice) {
                  cameraDevice.close();
                  cameraDevice = null;
              }
              if (null != imageReader) {
                  imageReader.close();
                  imageReader = null;
              }
          }
  
          public void resume() {
              Log.e(TAG, Debug.getLoc() + "onResume");
              startBackgroundThread();
              if (mTextureView.isAvailable()) {
                  openCamera();
              } else {
                  mTextureView.setSurfaceTextureListener(textureListener);
              }
          }
  
          public void pause() {
              Log.e(TAG, Debug.getLoc() + "onPause");
              stopBackgroundThread();
  
          }
  
      }
  
  }
  