Control commit
This commit is contained in:
parent
623c1bf95f
commit
01bc596284
3 changed files with 151 additions and 137 deletions
|
@ -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) -> {
|
||||||
|
|
|
@ -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,7 +172,6 @@ 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.
|
||||||
|
@ -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() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onImageAvailable(ImageReader reader) {
|
|
||||||
// Set the capture image
|
// Set the capture image
|
||||||
Image mImage = reader.acquireNextImage();
|
Image mImage = reader.acquireNextImage();
|
||||||
// Post the image to the thread so it can be converted to base64
|
// Post the image to the thread so it can be converted to base64
|
||||||
mBackgroundHandler.post(new ImageSaver(mImage, base64Callback));
|
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,27 +393,35 @@ 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);
|
|
||||||
|
|
||||||
|
setImageReaderListeners(largest);
|
||||||
|
|
||||||
|
boolean swappedDimensions = isSwappedDimensions(characteristics);
|
||||||
|
|
||||||
|
getOptimalPreviewSize(width, height, swappedDimensions, map, largest);
|
||||||
|
|
||||||
|
setPreviewAspectRatio();
|
||||||
|
|
||||||
|
mCameraId = cameraId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (CameraAccessException e) {
|
||||||
|
Log.e(TAG, "CameraAccessException@setUpCameraOutputs(): " + e.getMessage());
|
||||||
|
mCameraCallback.onCameraInitError(e.getMessage());
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
Log.e(TAG, "NullPointerException@setUpCameraOutputs(): " + e.getMessage());
|
||||||
|
mCameraCallback.onCameraInitError(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSwappedDimensions(CameraCharacteristics characteristics) {
|
||||||
// Find out if we need to swap dimension to get the preview size relative to sensor
|
// Find out if we need to swap dimension to get the preview size relative to sensor
|
||||||
// coordinate.
|
// coordinate.
|
||||||
int displayRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
|
int displayRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
|
||||||
|
@ -440,7 +444,23 @@ public class SegpassCamera {
|
||||||
default:
|
default:
|
||||||
Log.e(TAG, "Display rotation is invalid: " + displayRotation);
|
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();
|
Point displaySize = new Point();
|
||||||
mActivity.getWindowManager().getDefaultDisplay().getSize(displaySize);
|
mActivity.getWindowManager().getDefaultDisplay().getSize(displaySize);
|
||||||
int rotatedPreviewWidth = width;
|
int rotatedPreviewWidth = width;
|
||||||
|
@ -469,7 +489,16 @@ public class SegpassCamera {
|
||||||
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
|
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
|
||||||
rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
|
rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
|
||||||
maxPreviewHeight, largest);
|
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.
|
// We fit the aspect ratio of TextureView to the size of preview we picked.
|
||||||
int orientation = mActivity.getResources().getConfiguration().orientation;
|
int orientation = mActivity.getResources().getConfiguration().orientation;
|
||||||
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
@ -479,17 +508,6 @@ public class SegpassCamera {
|
||||||
mTextureView.setAspectRatio(
|
mTextureView.setAspectRatio(
|
||||||
mPreviewSize.getHeight(), mPreviewSize.getWidth());
|
mPreviewSize.getHeight(), mPreviewSize.getWidth());
|
||||||
}
|
}
|
||||||
|
|
||||||
mCameraId = cameraId;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (CameraAccessException e) {
|
|
||||||
Log.e(TAG, "CameraAccessException@setUpCameraOutputs(): " + e.getMessage());
|
|
||||||
mCameraCallback.onCameraInitError(e.getMessage());
|
|
||||||
} catch (NullPointerException e) {
|
|
||||||
Log.e(TAG, "NullPointerException@setUpCameraOutputs(): " + e.getMessage());
|
|
||||||
mCameraCallback.onCameraInitError(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue