From 01bc596284ebb0bb98766a77a191adb9317b361a Mon Sep 17 00:00:00 2001 From: Captain Arepa Date: Tue, 28 May 2024 09:36:36 -0400 Subject: [PATCH] Control commit --- .../cameraxtestappjava/CameraActivityNew.java | 1 - .../segpass/SegpassCamera.java | 267 +++++++++--------- .../exceptions/SegpassCameraException.java | 20 ++ 3 files changed, 151 insertions(+), 137 deletions(-) create mode 100644 app/src/main/java/com/example/cameraxtestappjava/segpass/camera/exceptions/SegpassCameraException.java diff --git a/app/src/main/java/com/example/cameraxtestappjava/CameraActivityNew.java b/app/src/main/java/com/example/cameraxtestappjava/CameraActivityNew.java index ac063c0..0837b39 100644 --- a/app/src/main/java/com/example/cameraxtestappjava/CameraActivityNew.java +++ b/app/src/main/java/com/example/cameraxtestappjava/CameraActivityNew.java @@ -43,7 +43,6 @@ public class CameraActivityNew extends AppCompatActivity implements ActivityComp mTextureView = binding.inCamera2.tvCameraTextureView; mSegpassCamera = new SegpassCamera(this, mTextureView, this, this); - mSegpassCamera.init(); setUpListeners(); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { diff --git a/app/src/main/java/com/example/cameraxtestappjava/segpass/SegpassCamera.java b/app/src/main/java/com/example/cameraxtestappjava/segpass/SegpassCamera.java index 74afdf0..d7afda0 100644 --- a/app/src/main/java/com/example/cameraxtestappjava/segpass/SegpassCamera.java +++ b/app/src/main/java/com/example/cameraxtestappjava/segpass/SegpassCamera.java @@ -33,9 +33,11 @@ import android.view.TextureView; import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; +import com.example.cameraxtestappjava.segpass.camera.exceptions.SegpassCameraException; import com.example.cameraxtestappjava.segpass.camera.utils.CompareSizesByArea; import com.example.cameraxtestappjava.segpass.camera.utils.ImageSaver; import com.example.cameraxtestappjava.segpass.camera.utils.ImageEncodingCallback; @@ -88,6 +90,11 @@ public class SegpassCamera { */ private static final int MAX_PREVIEW_HEIGHT = 1080; + /** + * Error constants + */ + private static final String ERROR_CAMERA_ACCESS = "Camera access error."; + /** * ID of the current {@link CameraDevice}. */ @@ -128,11 +135,6 @@ public class SegpassCamera { */ private ImageReader mImageReader; - /** - * This is the output file for our picture. - */ - private File mFile; - /** * This is the folder where the pictures will be saved. */ @@ -143,6 +145,12 @@ public class SegpassCamera { */ private String mBase64Value; + private final CaptureRequest.Key mCaptureAfModeKey = CaptureRequest.CONTROL_AF_MODE; + private final int mCaptureAfModeValue = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE; + + private final CaptureRequest.Key mCaptureAfTriggerKey = CaptureRequest.CONTROL_AF_TRIGGER; + private final int mCaptureAfTriggervalue = CaptureRequest.CONTROL_AF_TRIGGER_START; + /** * {@link CaptureRequest.Builder} for the camera preview */ @@ -164,10 +172,9 @@ public class SegpassCamera { private int mSensorOrientation; /** - * - * @param activity Parent activity where the camera is called from. - * @param textureView {@link AutoFitTextureView} where the camera preview is shown. - * @param listener {@link SegpassPermissionListener} that deals with camera and storage permissions. + * @param activity Parent activity where the camera is called from. + * @param textureView {@link AutoFitTextureView} where the camera preview is shown. + * @param listener {@link SegpassPermissionListener} that deals with camera and storage permissions. * @param cameraCallback {@link SegpassCameraCallback} that deals with operation results. */ public SegpassCamera(AppCompatActivity activity, AutoFitTextureView textureView, SegpassPermissionListener listener, SegpassCameraCallback cameraCallback) { @@ -177,13 +184,6 @@ public class SegpassCamera { mCameraCallback = cameraCallback; } - /** - * Method to initialize camera components. - */ - public void init() { - createFolder(); - } - /** * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a * {@link TextureView}. @@ -208,6 +208,7 @@ public class SegpassCamera { @Override public void onSurfaceTextureUpdated(SurfaceTexture texture) { + // No code implementation needed for this use case. } }; @@ -264,17 +265,11 @@ public class SegpassCamera { * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a * still image is ready to be saved. */ - private final ImageReader.OnImageAvailableListener mOnImageAvailableListener - = new ImageReader.OnImageAvailableListener() { - - @Override - public void onImageAvailable(ImageReader reader) { - // Set the capture image - Image mImage = reader.acquireNextImage(); - // Post the image to the thread so it can be converted to base64 - mBackgroundHandler.post(new ImageSaver(mImage, base64Callback)); - } - + private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = reader -> { + // Set the capture image + Image mImage = reader.acquireNextImage(); + // Post the image to the thread so it can be converted to base64 + mBackgroundHandler.post(new ImageSaver(mImage, base64Callback)); }; /** @@ -297,6 +292,7 @@ public class SegpassCamera { mBackgroundHandler = null; } catch (InterruptedException e) { Log.d(TAG, "InterruptedException@stopBackgroundThread(): " + e.getMessage()); + mBackgroundThread.interrupt(); } } @@ -397,88 +393,21 @@ public class SegpassCamera { CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); - // We don't use a back facing camera. - Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); - if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) { - continue; - } - - StreamConfigurationMap map = characteristics.get( - CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); - if (map == null) { - continue; - } + StreamConfigurationMap map = getStreamConfigurationMap(characteristics); + if (map == null) continue; // For still image captures, we use the largest available size. Size largest = Collections.max( Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizesByArea()); - mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), - ImageFormat.JPEG, /*maxImages*/2); - mImageReader.setOnImageAvailableListener( - mOnImageAvailableListener, mBackgroundHandler); - // Find out if we need to swap dimension to get the preview size relative to sensor - // coordinate. - int displayRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); - //noinspection ConstantConditions - mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); - boolean swappedDimensions = false; - switch (displayRotation) { - case Surface.ROTATION_0: - case Surface.ROTATION_180: - if (mSensorOrientation == 90 || mSensorOrientation == 270) { - swappedDimensions = true; - } - break; - case Surface.ROTATION_90: - case Surface.ROTATION_270: - if (mSensorOrientation == 0 || mSensorOrientation == 180) { - swappedDimensions = true; - } - break; - default: - Log.e(TAG, "Display rotation is invalid: " + displayRotation); - } + setImageReaderListeners(largest); - Point displaySize = new Point(); - mActivity.getWindowManager().getDefaultDisplay().getSize(displaySize); - int rotatedPreviewWidth = width; - int rotatedPreviewHeight = height; - int maxPreviewWidth = displaySize.x; - int maxPreviewHeight = displaySize.y; + boolean swappedDimensions = isSwappedDimensions(characteristics); - if (swappedDimensions) { - rotatedPreviewWidth = height; - rotatedPreviewHeight = width; - maxPreviewWidth = displaySize.y; - maxPreviewHeight = displaySize.x; - } + getOptimalPreviewSize(width, height, swappedDimensions, map, largest); - if (maxPreviewWidth > MAX_PREVIEW_WIDTH) { - maxPreviewWidth = MAX_PREVIEW_WIDTH; - } - - if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) { - maxPreviewHeight = MAX_PREVIEW_HEIGHT; - } - - // Danger, W.R.! Attempting to use too large a preview size could exceed the camera - // bus' bandwidth limitation, resulting in gorgeous previews but the storage of - // garbage capture data. - mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), - rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth, - maxPreviewHeight, largest); - - // We fit the aspect ratio of TextureView to the size of preview we picked. - int orientation = mActivity.getResources().getConfiguration().orientation; - if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - mTextureView.setAspectRatio( - mPreviewSize.getWidth(), mPreviewSize.getHeight()); - } else { - mTextureView.setAspectRatio( - mPreviewSize.getHeight(), mPreviewSize.getWidth()); - } + setPreviewAspectRatio(); mCameraId = cameraId; return; @@ -492,6 +421,95 @@ public class SegpassCamera { } } + private boolean isSwappedDimensions(CameraCharacteristics characteristics) { + // Find out if we need to swap dimension to get the preview size relative to sensor + // coordinate. + int displayRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); + //noinspection ConstantConditions + mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); + boolean swappedDimensions = false; + switch (displayRotation) { + case Surface.ROTATION_0: + case Surface.ROTATION_180: + if (mSensorOrientation == 90 || mSensorOrientation == 270) { + swappedDimensions = true; + } + break; + case Surface.ROTATION_90: + case Surface.ROTATION_270: + if (mSensorOrientation == 0 || mSensorOrientation == 180) { + swappedDimensions = true; + } + break; + default: + Log.e(TAG, "Display rotation is invalid: " + displayRotation); + } + return swappedDimensions; + } + + @Nullable + private static StreamConfigurationMap getStreamConfigurationMap(CameraCharacteristics characteristics) { + // We don't use a back facing camera. + Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); + + // If facis is null, return + if (facing == null) return null; + if (facing == CameraCharacteristics.LENS_FACING_BACK) return null; + + return characteristics.get( + CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + } + + private void getOptimalPreviewSize(int width, int height, boolean swappedDimensions, StreamConfigurationMap map, Size largest) { + Point displaySize = new Point(); + mActivity.getWindowManager().getDefaultDisplay().getSize(displaySize); + int rotatedPreviewWidth = width; + int rotatedPreviewHeight = height; + int maxPreviewWidth = displaySize.x; + int maxPreviewHeight = displaySize.y; + + if (swappedDimensions) { + rotatedPreviewWidth = height; + rotatedPreviewHeight = width; + maxPreviewWidth = displaySize.y; + maxPreviewHeight = displaySize.x; + } + + if (maxPreviewWidth > MAX_PREVIEW_WIDTH) { + maxPreviewWidth = MAX_PREVIEW_WIDTH; + } + + if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) { + maxPreviewHeight = MAX_PREVIEW_HEIGHT; + } + + // Danger, W.R.! Attempting to use too large a preview size could exceed the camera + // bus' bandwidth limitation, resulting in gorgeous previews but the storage of + // garbage capture data. + mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), + rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth, + maxPreviewHeight, largest); + } + + private void setImageReaderListeners(Size largest) { + mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), + ImageFormat.JPEG, /*maxImages*/2); + mImageReader.setOnImageAvailableListener( + mOnImageAvailableListener, mBackgroundHandler); + } + + private void setPreviewAspectRatio() { + // We fit the aspect ratio of TextureView to the size of preview we picked. + int orientation = mActivity.getResources().getConfiguration().orientation; + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + mTextureView.setAspectRatio( + mPreviewSize.getWidth(), mPreviewSize.getHeight()); + } else { + mTextureView.setAspectRatio( + mPreviewSize.getHeight(), mPreviewSize.getWidth()); + } + } + /** * Opens the camera specified by {@link SegpassCamera#mCameraId}. */ @@ -507,7 +525,7 @@ public class SegpassCamera { CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE); try { if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { - throw new RuntimeException("Time out waiting to lock camera opening."); + throw new SegpassCameraException("Time out waiting to lock camera opening."); } manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler); } catch (CameraAccessException e) { @@ -515,7 +533,7 @@ public class SegpassCamera { mCameraCallback.onCameraInitError(e.getMessage()); } catch (InterruptedException e) { Log.e(TAG, "InterruptedException@openCamera(): " + e.getMessage()); - throw new RuntimeException("Interrupted while trying to lock camera opening.", e); + throw new SegpassCameraException("Interrupted while trying to lock camera opening.", e); } } @@ -538,7 +556,7 @@ public class SegpassCamera { mImageReader = null; } } catch (InterruptedException e) { - throw new RuntimeException("Interrupted while trying to lock camera closing.", e); + throw new SegpassCameraException("Interrupted while trying to lock camera closing.", e); } finally { mCameraOpenCloseLock.release(); } @@ -578,11 +596,9 @@ public class SegpassCamera { mCaptureSession = cameraCaptureSession; try { // Auto focus should be continuous for camera preview. - mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, - CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); + mPreviewRequestBuilder.set(mCaptureAfModeKey, mCaptureAfModeValue); // Trigger AF - mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, - CaptureRequest.CONTROL_AF_TRIGGER_START); + mPreviewRequestBuilder.set(mCaptureAfTriggerKey, mCaptureAfTriggervalue); // Finally, we start displaying the camera preview. mPreviewRequest = mPreviewRequestBuilder.build(); @@ -602,8 +618,8 @@ public class SegpassCamera { }, null ); } catch (CameraAccessException e) { - Log.e(TAG, "createCameraPreviewSession(): " + e. getMessage()); - mCameraCallback.onCameraInitError("Camera access error."); + Log.e(TAG, "createCameraPreviewSession(): " + e.getMessage()); + mCameraCallback.onCameraInitError(ERROR_CAMERA_ACCESS); } } @@ -632,7 +648,7 @@ public class SegpassCamera { (float) viewHeight / mPreviewSize.getHeight(), (float) viewWidth / mPreviewSize.getWidth()); matrix.postScale(scale, scale, centerX, centerY); - matrix.postRotate(90 * (rotation - 2), centerX, centerY); + matrix.postRotate(90 * (rotation - 2L), centerX, centerY); } else if (Surface.ROTATION_180 == rotation) { matrix.postRotate(180, centerX, centerY); } @@ -706,8 +722,8 @@ public class SegpassCamera { try { session.capture(captureBuilder.build(), captureListener, mBackgroundHandler); } catch (CameraAccessException e) { - Log.e(TAG, "onConfigured@takePicture(): " + e. getMessage()); - mCameraCallback.onCameraInitError("Camera access error."); + Log.e(TAG, "onConfigured@takePicture(): " + e.getMessage()); + mCameraCallback.onCameraInitError(ERROR_CAMERA_ACCESS); } } @@ -716,9 +732,10 @@ public class SegpassCamera { } }, mBackgroundHandler); } catch (CameraAccessException e) { - Log.e(TAG, "takePicture(): " + e. getMessage()); - mCameraCallback.onCameraInitError("Camera access error."); + Log.e(TAG, "takePicture(): " + e.getMessage()); + mCameraCallback.onCameraInitError(ERROR_CAMERA_ACCESS); } + } private int getOrientation(int rotation) { @@ -728,26 +745,4 @@ public class SegpassCamera { // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360; } - - /** - * Creates the custom folder {@link SegpassCamera#mFileFolder} where pictures are saved. - */ - public void createFolder() { - PackageManager pm = mActivity.getPackageManager(); - String folderName; - - try { - ApplicationInfo ai = pm.getApplicationInfo(mActivity.getPackageName(), 0); - folderName = pm.getApplicationLabel(ai).toString(); - } catch (Exception e) { - Log.e(TAG, "Can't find the package name."); - folderName = "CustomCameraLibraryFolder"; - } - - File folder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); - mFileFolder = new File(folder, folderName); - if (!mFileFolder.exists()) { - mFileFolder.mkdirs(); - } - } } diff --git a/app/src/main/java/com/example/cameraxtestappjava/segpass/camera/exceptions/SegpassCameraException.java b/app/src/main/java/com/example/cameraxtestappjava/segpass/camera/exceptions/SegpassCameraException.java new file mode 100644 index 0000000..d180274 --- /dev/null +++ b/app/src/main/java/com/example/cameraxtestappjava/segpass/camera/exceptions/SegpassCameraException.java @@ -0,0 +1,20 @@ +package com.example.cameraxtestappjava.segpass.camera.exceptions; + +public class SegpassCameraException extends RuntimeException { + + // Optional: You can define a constructor with a message argument + public SegpassCameraException(String message) { + super(message); + } + + // Optional: You can define a constructor with a message and a cause (throwable) + public SegpassCameraException(String message, Throwable cause) { + super(message, cause); + } + + // Optional: You can define custom methods specific to your exception + public String getCustomDetails() { + // ... Add logic to return additional details related to the exception + return "This is a custom detail"; + } +}