Control commit

This commit is contained in:
Captain Arepa 2024-05-28 09:36:36 -04:00
parent 623c1bf95f
commit 01bc596284
3 changed files with 151 additions and 137 deletions

View file

@ -43,7 +43,6 @@ public class CameraActivityNew extends AppCompatActivity implements ActivityComp
mTextureView = binding.inCamera2.tvCameraTextureView; mTextureView = binding.inCamera2.tvCameraTextureView;
mSegpassCamera = new SegpassCamera(this, mTextureView, this, this); mSegpassCamera = new SegpassCamera(this, mTextureView, this, this);
mSegpassCamera.init();
setUpListeners(); setUpListeners();
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {

View file

@ -33,9 +33,11 @@ import android.view.TextureView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat; 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.CompareSizesByArea;
import com.example.cameraxtestappjava.segpass.camera.utils.ImageSaver; import com.example.cameraxtestappjava.segpass.camera.utils.ImageSaver;
import com.example.cameraxtestappjava.segpass.camera.utils.ImageEncodingCallback; import com.example.cameraxtestappjava.segpass.camera.utils.ImageEncodingCallback;
@ -88,6 +90,11 @@ public class SegpassCamera {
*/ */
private static final int MAX_PREVIEW_HEIGHT = 1080; 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}. * ID of the current {@link CameraDevice}.
*/ */
@ -128,11 +135,6 @@ public class SegpassCamera {
*/ */
private ImageReader mImageReader; private ImageReader mImageReader;
/**
* This is the output file for our picture.
*/
private File mFile;
/** /**
* This is the folder where the pictures will be saved. * This is the folder where the pictures will be saved.
*/ */
@ -143,6 +145,12 @@ public class SegpassCamera {
*/ */
private String mBase64Value; private String mBase64Value;
private final CaptureRequest.Key<Integer> mCaptureAfModeKey = CaptureRequest.CONTROL_AF_MODE;
private final int mCaptureAfModeValue = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
private final CaptureRequest.Key<Integer> mCaptureAfTriggerKey = CaptureRequest.CONTROL_AF_TRIGGER;
private final int mCaptureAfTriggervalue = CaptureRequest.CONTROL_AF_TRIGGER_START;
/** /**
* {@link CaptureRequest.Builder} for the camera preview * {@link CaptureRequest.Builder} for the camera preview
*/ */
@ -164,10 +172,9 @@ public class SegpassCamera {
private int mSensorOrientation; private int mSensorOrientation;
/** /**
* * @param activity Parent activity where the camera is called from.
* @param activity Parent activity where the camera is called from. * @param textureView {@link AutoFitTextureView} where the camera preview is shown.
* @param textureView {@link AutoFitTextureView} where the camera preview is shown. * @param listener {@link SegpassPermissionListener} that deals with camera and storage permissions.
* @param listener {@link SegpassPermissionListener} that deals with camera and storage permissions.
* @param cameraCallback {@link SegpassCameraCallback} that deals with operation results. * @param cameraCallback {@link SegpassCameraCallback} that deals with operation results.
*/ */
public SegpassCamera(AppCompatActivity activity, AutoFitTextureView textureView, SegpassPermissionListener listener, SegpassCameraCallback cameraCallback) { public SegpassCamera(AppCompatActivity activity, AutoFitTextureView textureView, SegpassPermissionListener listener, SegpassCameraCallback cameraCallback) {
@ -177,13 +184,6 @@ public class SegpassCamera {
mCameraCallback = cameraCallback; mCameraCallback = cameraCallback;
} }
/**
* Method to initialize camera components.
*/
public void init() {
createFolder();
}
/** /**
* {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a
* {@link TextureView}. * {@link TextureView}.
@ -208,6 +208,7 @@ public class SegpassCamera {
@Override @Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) { 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 * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
* still image is ready to be saved. * still image is ready to be saved.
*/ */
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = reader -> {
= new ImageReader.OnImageAvailableListener() { // Set the capture image
Image mImage = reader.acquireNextImage();
@Override // Post the image to the thread so it can be converted to base64
public void onImageAvailable(ImageReader reader) { mBackgroundHandler.post(new ImageSaver(mImage, base64Callback));
// 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; mBackgroundHandler = null;
} catch (InterruptedException e) { } catch (InterruptedException e) {
Log.d(TAG, "InterruptedException@stopBackgroundThread(): " + e.getMessage()); Log.d(TAG, "InterruptedException@stopBackgroundThread(): " + e.getMessage());
mBackgroundThread.interrupt();
} }
} }
@ -397,88 +393,21 @@ public class SegpassCamera {
CameraCharacteristics characteristics CameraCharacteristics characteristics
= manager.getCameraCharacteristics(cameraId); = manager.getCameraCharacteristics(cameraId);
// We don't use a back facing camera. StreamConfigurationMap map = getStreamConfigurationMap(characteristics);
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); if (map == null) continue;
if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) {
continue;
}
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
continue;
}
// For still image captures, we use the largest available size. // For still image captures, we use the largest available size.
Size largest = Collections.max( Size largest = Collections.max(
Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
new CompareSizesByArea()); 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 setImageReaderListeners(largest);
// 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);
}
Point displaySize = new Point(); boolean swappedDimensions = isSwappedDimensions(characteristics);
mActivity.getWindowManager().getDefaultDisplay().getSize(displaySize);
int rotatedPreviewWidth = width;
int rotatedPreviewHeight = height;
int maxPreviewWidth = displaySize.x;
int maxPreviewHeight = displaySize.y;
if (swappedDimensions) { getOptimalPreviewSize(width, height, swappedDimensions, map, largest);
rotatedPreviewWidth = height;
rotatedPreviewHeight = width;
maxPreviewWidth = displaySize.y;
maxPreviewHeight = displaySize.x;
}
if (maxPreviewWidth > MAX_PREVIEW_WIDTH) { setPreviewAspectRatio();
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());
}
mCameraId = cameraId; mCameraId = cameraId;
return; 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}. * Opens the camera specified by {@link SegpassCamera#mCameraId}.
*/ */
@ -507,7 +525,7 @@ public class SegpassCamera {
CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE); CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
try { try {
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { 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); manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) { } catch (CameraAccessException e) {
@ -515,7 +533,7 @@ public class SegpassCamera {
mCameraCallback.onCameraInitError(e.getMessage()); mCameraCallback.onCameraInitError(e.getMessage());
} catch (InterruptedException e) { } catch (InterruptedException e) {
Log.e(TAG, "InterruptedException@openCamera(): " + e.getMessage()); 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; mImageReader = null;
} }
} catch (InterruptedException e) { } 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 { } finally {
mCameraOpenCloseLock.release(); mCameraOpenCloseLock.release();
} }
@ -578,11 +596,9 @@ public class SegpassCamera {
mCaptureSession = cameraCaptureSession; mCaptureSession = cameraCaptureSession;
try { try {
// Auto focus should be continuous for camera preview. // Auto focus should be continuous for camera preview.
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, mPreviewRequestBuilder.set(mCaptureAfModeKey, mCaptureAfModeValue);
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// Trigger AF // Trigger AF
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, mPreviewRequestBuilder.set(mCaptureAfTriggerKey, mCaptureAfTriggervalue);
CaptureRequest.CONTROL_AF_TRIGGER_START);
// Finally, we start displaying the camera preview. // Finally, we start displaying the camera preview.
mPreviewRequest = mPreviewRequestBuilder.build(); mPreviewRequest = mPreviewRequestBuilder.build();
@ -602,8 +618,8 @@ public class SegpassCamera {
}, null }, null
); );
} catch (CameraAccessException e) { } catch (CameraAccessException e) {
Log.e(TAG, "createCameraPreviewSession(): " + e. getMessage()); Log.e(TAG, "createCameraPreviewSession(): " + e.getMessage());
mCameraCallback.onCameraInitError("Camera access error."); mCameraCallback.onCameraInitError(ERROR_CAMERA_ACCESS);
} }
} }
@ -632,7 +648,7 @@ public class SegpassCamera {
(float) viewHeight / mPreviewSize.getHeight(), (float) viewHeight / mPreviewSize.getHeight(),
(float) viewWidth / mPreviewSize.getWidth()); (float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY); 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) { } else if (Surface.ROTATION_180 == rotation) {
matrix.postRotate(180, centerX, centerY); matrix.postRotate(180, centerX, centerY);
} }
@ -706,8 +722,8 @@ public class SegpassCamera {
try { try {
session.capture(captureBuilder.build(), captureListener, mBackgroundHandler); session.capture(captureBuilder.build(), captureListener, mBackgroundHandler);
} catch (CameraAccessException e) { } catch (CameraAccessException e) {
Log.e(TAG, "onConfigured@takePicture(): " + e. getMessage()); Log.e(TAG, "onConfigured@takePicture(): " + e.getMessage());
mCameraCallback.onCameraInitError("Camera access error."); mCameraCallback.onCameraInitError(ERROR_CAMERA_ACCESS);
} }
} }
@ -716,9 +732,10 @@ public class SegpassCamera {
} }
}, mBackgroundHandler); }, mBackgroundHandler);
} catch (CameraAccessException e) { } catch (CameraAccessException e) {
Log.e(TAG, "takePicture(): " + e. getMessage()); Log.e(TAG, "takePicture(): " + e.getMessage());
mCameraCallback.onCameraInitError("Camera access error."); mCameraCallback.onCameraInitError(ERROR_CAMERA_ACCESS);
} }
} }
private int getOrientation(int rotation) { 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. // For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360; 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();
}
}
} }

View file

@ -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";
}
}