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;
|
||||
mSegpassCamera = new SegpassCamera(this, mTextureView, this, this);
|
||||
mSegpassCamera.init();
|
||||
setUpListeners();
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
||||
|
|
|
@ -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<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
|
||||
*/
|
||||
|
@ -164,7 +172,6 @@ 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.
|
||||
|
@ -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) {
|
||||
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,27 +393,35 @@ 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);
|
||||
|
||||
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
|
||||
// coordinate.
|
||||
int displayRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
|
||||
|
@ -440,7 +444,23 @@ public class SegpassCamera {
|
|||
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;
|
||||
|
@ -469,7 +489,16 @@ public class SegpassCamera {
|
|||
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) {
|
||||
|
@ -479,17 +508,6 @@ public class SegpassCamera {
|
|||
mTextureView.setAspectRatio(
|
||||
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);
|
||||
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();
|
||||
|
@ -603,7 +619,7 @@ public class SegpassCamera {
|
|||
);
|
||||
} catch (CameraAccessException e) {
|
||||
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) 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);
|
||||
}
|
||||
|
@ -707,7 +723,7 @@ public class SegpassCamera {
|
|||
session.capture(captureBuilder.build(), captureListener, mBackgroundHandler);
|
||||
} catch (CameraAccessException e) {
|
||||
Log.e(TAG, "onConfigured@takePicture(): " + e.getMessage());
|
||||
mCameraCallback.onCameraInitError("Camera access error.");
|
||||
mCameraCallback.onCameraInitError(ERROR_CAMERA_ACCESS);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -717,8 +733,9 @@ public class SegpassCamera {
|
|||
}, mBackgroundHandler);
|
||||
} catch (CameraAccessException e) {
|
||||
Log.e(TAG, "takePicture(): " + e.getMessage());
|
||||
mCameraCallback.onCameraInitError("Camera access error.");
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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