Compare commits

...

34 commits

Author SHA1 Message Date
94f866d7c6 Control commit 2024-06-07 15:48:05 -04:00
756dedd737 Control commit 2024-06-07 15:20:59 -04:00
6166aed715 Control commit 2024-06-07 14:46:56 -04:00
bbbe4344fe Control commit 2024-06-06 12:05:33 -04:00
df52951707 Control commit 2024-05-31 14:52:20 -04:00
6fc8abde7f Control commit 2024-05-31 14:50:24 -04:00
10540e3821 Control commit 2024-05-31 14:22:04 -04:00
1bbf632804 Control commit 2024-05-31 14:20:03 -04:00
103b11c3be Control commit 2024-05-31 14:18:11 -04:00
ba24fbdbae Control commit 2024-05-31 14:00:43 -04:00
0f11ca12bc Control commit 2024-05-31 13:41:34 -04:00
fbe834b8e2 Control commit 2024-05-28 16:14:39 -04:00
986cf8cf9a Control commit 2024-05-28 15:48:49 -04:00
35a3f6cba3 Control commit 2024-05-28 15:24:22 -04:00
1ad060a816 Control commit 2024-05-28 15:00:34 -04:00
5f8cfd5a1f Control commit 2024-05-28 14:40:30 -04:00
ebdff6b407 Control commit 2024-05-28 14:10:47 -04:00
01bc596284 Control commit 2024-05-28 09:36:36 -04:00
623c1bf95f control commit 2024-05-27 16:44:48 -04:00
fcde6328a5 control commit 2024-05-27 16:41:43 -04:00
09670b4a33 control commit 2024-05-27 13:10:33 -04:00
51bd224ce5 control commit 2024-05-27 08:17:07 -04:00
2f449a55c8 control commit 2024-05-23 16:12:23 -04:00
5aeeee0b44 stuff 2024-05-23 16:06:35 -04:00
11d14f2517 AF 2024-05-23 16:00:29 -04:00
6fdb9f4f7b AF 2024-05-23 15:12:26 -04:00
43415be913 AF 2024-05-23 13:57:23 -04:00
84d1dcf058 shit 2024-05-23 13:11:20 -04:00
711c362486 control commit 2024-05-23 13:07:41 -04:00
f254d77dd4 control commit 2024-05-23 11:28:35 -04:00
807a8619a8 control commit 2024-05-22 16:12:51 -04:00
022542cc72 Control commit 2024-05-22 14:12:51 -04:00
674664e059 Control commit 2024-05-22 11:08:17 -04:00
ee00263034 camera2 implementation (WIP) 2024-05-22 10:49:41 -04:00
34 changed files with 1664 additions and 779 deletions

View file

@ -48,4 +48,5 @@ dependencies {
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
testImplementation libs.robolectric
}

View file

@ -7,7 +7,8 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
android:maxSdkVersion="32"
tools:ignore="ScopedStorage" />
<application
android:allowBackup="true"
@ -19,6 +20,15 @@
android:supportsRtl="true"
android:theme="@style/Theme.CameraXTestAppJava"
tools:targetApi="31">
<activity
android:name=".SegpassCameraActivity"
android:exported="false" />
<activity
android:name=".CameraActivityNew"
android:exported="false" />
<activity
android:name=".CameraActivityOld"
android:exported="false" />
<activity
android:name=".Camera2Activity"
android:exported="false" />

View file

@ -0,0 +1,124 @@
package com.example.cameraxtestappjava;
import static com.example.cameraxtestappjava.segpass.SegpassCamera.REQUEST_CAMERA_PERMISSION;
import android.annotation.SuppressLint;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import android.Manifest;
import android.content.pm.PackageManager;
import com.example.cameraxtestappjava.segpass.camera.view.AutoFitTextureView;
import com.example.cameraxtestappjava.segpass.camera.utils.SegpassCameraCallback;
import com.example.cameraxtestappjava.segpass.camera.utils.SegpassPermissionCallback;
import com.example.cameraxtestappjava.databinding.ActivityCameraNewBinding;
import com.example.cameraxtestappjava.segpass.SegpassCamera;
public class CameraActivityNew extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback, SegpassCameraCallback, SegpassPermissionCallback {
private static final String TAG = "CameraActivityNew";
ActivityCameraNewBinding binding;
SegpassCamera mSegpassCamera;
AutoFitTextureView mTextureView;
@SuppressLint("SourceLockedOrientationActivity")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
binding = ActivityCameraNewBinding.inflate(getLayoutInflater());
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
setContentView(binding.getRoot());
mTextureView = binding.inCamera2.tvCameraTextureView;
mSegpassCamera = new SegpassCamera(this, mTextureView, this, this);
setUpListeners();
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
@Override
public void onResume() {
super.onResume();
mSegpassCamera.resumeCamera();
}
@Override
public void onPause() {
mSegpassCamera.pauseCamera();
super.onPause();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults.length != 2 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
onPermissionDenied();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
private void setUpListeners() {
binding.inCamera2.btnTakePhoto.setOnClickListener(v -> mSegpassCamera.takePicture());
}
/**
* Camera callback
*/
@Override
public void onPictureTakenSuccess(String base64) {
mSegpassCamera.showToast("Base64 generated.");
}
@Override
public void onPictureTakenFail(String error) {
mSegpassCamera.showToast(error);
}
/**
* Camera state callback (just to validate if there was an error while initializing the camera)
*/
@Override
public void onCameraInitError(String errorMessage) {
mSegpassCamera.showToast(errorMessage);
}
/**
* Permissions callbck
*/
@Override
public void onPermissionRequest() {
requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CAMERA_PERMISSION);
}
@Override
public void onPermissionGranted() {
// Do nothing, use camera.
}
@Override
public void onPermissionDenied() {
mSegpassCamera.showToast("No permissions granted, closing camera.");
finish();
}
}

View file

@ -0,0 +1,35 @@
package com.example.cameraxtestappjava;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.example.cameraxtestappjava.databinding.ActivityCameraBinding;
public class CameraActivityOld extends AppCompatActivity {
ActivityCameraBinding binding;
private static final String TAG = "CameraActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
binding = ActivityCameraBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
}

View file

@ -1,24 +1,15 @@
package com.example.cameraxtestappjava;
package com.example.cameraxtestappjava;
import static com.example.cameraxtestappjava.camera.CustomCamera2.REQUEST_CAMERA_PERMISSION;
import android.content.pm.PackageManager;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.TextureView;
import android.widget.ImageButton;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.example.cameraxtestappjava.camera.CustomCamera2;
import com.example.cameraxtestappjava.camera.CustomCameraCallback;
import com.example.cameraxtestappjava.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
@ -28,7 +19,6 @@ public class MainActivity extends AppCompatActivity {
// Camera2
private static final String TAG = "MainActivity";
CustomCamera2 mCustomCamera2;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -38,7 +28,7 @@ public class MainActivity extends AppCompatActivity {
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setUpCamera2();
setListeners();
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
@ -47,61 +37,17 @@ public class MainActivity extends AppCompatActivity {
});
}
// Camera 2
private void setUpCamera2() {
// Initialize visual elements in host app
TextureView mTextureView = binding.c2Camera.tvCameraTextureView;
ImageButton mTakePictureButton = binding.c2Camera.btnTakepicture;
// Initialized CustomCamera2 in host app
mCustomCamera2 = new CustomCamera2(this, mTextureView);
mCustomCamera2.init(); // Do not skip this!!!
mTakePictureButton.setOnClickListener(v -> mCustomCamera2.takePicture(new CustomCameraCallback() {
@Override
public void onPictureTakenSuccess(String message) {
Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show();
}
@Override
public void onPictureTakenFailError(String error) {
Toast.makeText(MainActivity.this, error, Toast.LENGTH_LONG).show();
}
}));
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
// close the app
Toast.makeText(MainActivity.this, "Sorry!!!, you can't use this app without granting permission", Toast.LENGTH_LONG).show();
finish();
}
}
private void setListeners() {
binding.goToCamera.setOnClickListener(v -> {
Intent intent = new Intent(this, SegpassCameraActivity.class);
startActivity(intent);
});
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume");
mCustomCamera2.resumeCamera();
setListeners();
}
@Override
protected void onPause() {
Log.d(TAG, "onPause");
mCustomCamera2.pauseCamera();
super.onPause();
}
@Override
protected void onDestroy() {
Log.d(TAG, "onPause");
mCustomCamera2.destroyCamera();
super.onDestroy();
}
}

View file

@ -0,0 +1,122 @@
package com.example.cameraxtestappjava;
import static com.example.cameraxtestappjava.segpass.SegpassCamera.REQUEST_CAMERA_PERMISSION;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.example.cameraxtestappjava.databinding.ActivitySegpassCameraBinding;
import com.example.cameraxtestappjava.segpass.camera.utils.SegpassCameraCallback;
import com.example.cameraxtestappjava.segpass.camera.utils.SegpassPermissionCallback;
import com.example.cameraxtestappjava.segpass.camera.view.SegpassCameraLayout;
public class SegpassCameraActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback, SegpassCameraCallback, SegpassPermissionCallback {
ActivitySegpassCameraBinding binding;
SegpassCameraLayout cameraLayout;
@SuppressLint("SourceLockedOrientationActivity")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
binding = ActivitySegpassCameraBinding.inflate(getLayoutInflater());
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
setContentView(binding.getRoot());
setCamera();
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
private void setCamera() {
cameraLayout = binding.sclCameraLayout; // Bind SegpassCameraLayout to element in view
cameraLayout.init(this, this, this); // Init SegpassCamera
}
@Override
public void onResume() {
super.onResume();
cameraLayout.resumeCamera();
}
@Override
public void onPause() {
cameraLayout.pauseCamera();
super.onPause();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults.length != 2 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
onPermissionDenied();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
/**
* Camera callback
*/
@Override
public void onPictureTakenSuccess(String base64) {
cameraLayout.showToast("Base64 generated.");
this.finish();
}
@Override
public void onPictureTakenFail(String error) {
cameraLayout.showToast(error);
this.finish();
}
/**
* Camera state callback (just to validate if there was an error while initializing the camera)
*/
@Override
public void onCameraInitError(String errorMessage) {
cameraLayout.showToast(errorMessage);
this.finish();
}
/**
* Permissions callbck
*/
@Override
public void onPermissionRequest() {
requestPermissions(new String[]{android.Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CAMERA_PERMISSION);
}
@Override
public void onPermissionGranted() {
// Do nothing, use camera.
}
@Override
public void onPermissionDenied() {
cameraLayout.showToast("No permissions granted, closing camera.");
finish();
}
}

View file

@ -1,108 +0,0 @@
package com.example.cameraxtestappjava.camera;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import androidx.annotation.NonNull;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.ExecutionException;
public class CustomCamera {
ProcessCameraProvider mCameraProvider;
ImageCapture imageCapture;
ImageCapture.OutputFileOptions outputFileOptions;
ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
ContentValues contentValues;
String fileName;
Uri tempFileUri;
Context mContext;
PreviewView mPreviewView;
public CustomCamera(Context context, PreviewView previewView) {
mContext = context;
mPreviewView = previewView;
}
public void setUpCamera() {
cameraProviderFuture = ProcessCameraProvider.getInstance(mContext);
cameraProviderFuture.addListener(() -> {
try {
mCameraProvider = cameraProviderFuture.get();
startCustomCamera();
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
}, ContextCompat.getMainExecutor(mContext)); // por parametro
}
private void startCustomCamera() {
CameraSelector cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA;
Preview preview = new Preview.Builder().build();
preview.setSurfaceProvider(mPreviewView.getSurfaceProvider());
imageCapture = new ImageCapture.Builder().build();
try {
mCameraProvider.unbindAll();
mCameraProvider.bindToLifecycle((LifecycleOwner) mContext, cameraSelector, preview, imageCapture);
} catch (Exception e) {
e.printStackTrace();
}
}
@NonNull
private ImageCapture.OutputFileOptions getOutputFileOptions(String name) {
contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraXTestApp");
}
return new ImageCapture.OutputFileOptions.Builder(
mContext.getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues
).build();
}
public void capturePhoto(CustomCameraCallback callback) {
if (imageCapture == null) return;
fileName = System.currentTimeMillis() + "";
outputFileOptions = getOutputFileOptions(fileName);
imageCapture.takePicture(outputFileOptions, ContextCompat.getMainExecutor(mContext), new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
tempFileUri = outputFileResults.getSavedUri();
callback.onPictureTakenSuccess("Image saved! " + tempFileUri);
}
@Override
public void onError(@NonNull ImageCaptureException exception) {
callback.onPictureTakenFailError("Image Not Saved " + exception.getMessage());
}
});
}
}

View file

@ -1,503 +0,0 @@
package com.example.cameraxtestappjava.camera;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
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.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
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.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class CustomCamera2 {
private static final String TAG = "SegpassCamera";
Context mContext;
AppCompatActivity mActivity;
private TextureView mTextureView;
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
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 String mCameraId;
private String mDefaultCameraId = "0";
private Size mPreviewSize;
private int mTotalRotation;
protected CameraDevice mCameraDevice;
protected String[] mCameraIds;
protected CameraManager mCameraManager;
protected CameraCaptureSession mCameraCaptureSessions;
private Size mImageDimension;
private ImageReader mImageReader;
private File mFileFolder;
private File mFile;
public static final int REQUEST_CAMERA_PERMISSION = 200;
private Handler mBackgroundHandler;
private HandlerThread mBackgroundThread;
private CaptureRequest.Builder mCaptureRequestBuilder;
private static class CompareSizeByArea implements Comparator<Size> {
@Override
public int compare(Size ths, Size rhs) {
return Long.signum((long) ths.getWidth() * ths.getHeight() /
(long) rhs.getWidth() * rhs.getHeight());
}
}
public CustomCamera2(Context context, TextureView textureView) {
mContext = context;
mTextureView = textureView;
}
public CustomCamera2(AppCompatActivity activity, TextureView textureView) {
mActivity = activity;
mTextureView = textureView;
}
public void init() {
createFolder();
setUpListeners();
}
private void setUpListeners() {
mTextureView.setSurfaceTextureListener(textureListener);
}
public TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
//open your camera here
setupCamera(width, height);
connectCamera();
startPreview();
}
@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
// Transform you image captured size according to the surface width and height
closeCamera();
setupCamera(width, height);
connectCamera();
startPreview();
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
//Log.d(TAG, "Texture Updated!");
}
};
// Camera Device State Callback
private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
//This is called when the camera is open
Log.e(TAG, "onOpened");
mCameraDevice = camera; // ???????
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
mCameraDevice.close();
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
mCameraDevice.close();
mCameraDevice = null;
}
};
//Background Thread
public void startBackgroundThread() {
mBackgroundThread = new HandlerThread("Camera Background");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
public void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// Setup Camera
private CameraCharacteristics getCameraCharacteristics() {
try {
for (String cameraId : mCameraIds) {
// Local cc
CameraCharacteristics cc = mCameraManager.getCameraCharacteristics(cameraId);
// If no LENS_FACING characteristic is found, NullPointerException
if (cc.get(CameraCharacteristics.LENS_FACING) == null)
throw new NullPointerException();
if (cc.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT) {
mCameraId = cameraId;
} else {
mCameraId = mDefaultCameraId;
}
}
return mCameraManager.getCameraCharacteristics(mCameraId);
} catch (NullPointerException | CameraAccessException e) {
return null;
}
}
public void setupCamera(int width, int height) {
Log.d(TAG, "ENTRO ACA " + width + " " + height);
// Shenanigans for the TextureView
mCameraManager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
try {
mCameraIds = mCameraManager.getCameraIdList();
Log.d(TAG, "eeeeeee " + mCameraIds.length);
for (String cameraId : mCameraManager.getCameraIdList()) {
//tv.append(cameraId + "\n");
}
} catch (CameraAccessException e) {
//tv.append("ERROR: CAMERA NOT FOUND\n");
}
try {
CameraCharacteristics cameraCharacteristics = getCameraCharacteristics();
// If no characteristics are found, throw a CameraAccessException
if (cameraCharacteristics == null)
throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
// Shenanigans
StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
int deviceOrientation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
mTotalRotation = sensorToDeviceRotation(cameraCharacteristics, deviceOrientation);
boolean swapRotation = mTotalRotation == 90 || mTotalRotation == 270;
int rotatedWidth = width;
int rotatedHeight = height;
if (swapRotation) {
rotatedWidth = height;
rotatedHeight = width;
}
assert map != null;
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), rotatedWidth, rotatedHeight);
//mVideoSize = chooseOptimalSize(map.getOutputSizes(MediaRecorder.class), rotatedWidth, rotatedHeight);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
//Connect Camera
public void connectCamera() {
try {
if (ActivityCompat.checkSelfPermission(mActivity, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(mActivity, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(mActivity, new String[]{android.Manifest.permission.CAMERA, android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CAMERA_PERMISSION);
return;
}
mCameraManager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
// Start Preview
public void startPreview() {
SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
assert surfaceTexture != null;
surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
Surface previewSurface = new Surface(surfaceTexture);
try {
mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mCaptureRequestBuilder.addTarget(previewSurface);
mCaptureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
mCaptureRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON);
mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
try {
cameraCaptureSession.setRepeatingRequest(mCaptureRequestBuilder.build(), null, mBackgroundHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "Camera can be accessed.");
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT).show();
}
}, null);
} catch (CameraAccessException e) {
Log.e(TAG, "DAMN");
}
}
//Take Picture
public void takePicture(CustomCameraCallback cameraCallback) {
if (mCameraDevice == null) {
Log.e(TAG, "cameraDevice is null");
return;
}
try {
CameraCharacteristics characteristics = getCameraCharacteristics();
if (characteristics == null)
throw new CameraAccessException(CameraAccessException.CAMERA_ERROR);
Size[] jpegSizes = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(ImageFormat.JPEG);
if (jpegSizes == null) throw new NullPointerException("Error taking picture");
int width = 640;
int height = 480;
if (jpegSizes.length > 0) {
width = jpegSizes[0].getWidth();
height = jpegSizes[0].getHeight();
}
ImageReader mReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
List<Surface> outputSurfaces = new ArrayList<Surface>(2);
outputSurfaces.add(mReader.getSurface());
outputSurfaces.add(new Surface(mTextureView.getSurfaceTexture()));
final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mReader.getSurface());
captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
// Orientation
int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
String fileName = "IMG_" + System.currentTimeMillis() + ".jpg";
mFile = new File(mFileFolder + "/" + fileName); // Ver como hacer para guardar en la carpeta de la app
ImageReader.OnImageAvailableListener readerListener = iReader -> {
try (Image image = iReader.acquireLatestImage()) {
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.capacity()];
buffer.get(bytes);
savePicture(bytes);
} catch (IOException e) {
cameraCallback.onPictureTakenFailError(e.getMessage());
}
};
mReader.setOnImageAvailableListener(readerListener, mBackgroundHandler);
final CameraCaptureSession.CaptureCallback captureListener = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
Log.d(TAG, "file");
//Toast.makeText(mActivity, "Saved:" + mFile, Toast.LENGTH_SHORT).show();
createCameraPreview();
cameraCallback.onPictureTakenSuccess("Saved:" + mFile);
}
};
mCameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
try {
session.capture(captureBuilder.build(), captureListener, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
}
}, mBackgroundHandler);
} catch (CameraAccessException e) {
cameraCallback.onPictureTakenFailError(e.getMessage());
}
}
private void savePicture(byte[] bytes) throws IOException {
OutputStream output = null;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
output = Files.newOutputStream(mFile.toPath());
} else {
output = new FileOutputStream(mFile);
}
output.write(bytes);
} finally {
if (output != null) {
output.close();
}
}
}
protected void createCameraPreview() {
try {
CameraCharacteristics cc = getCameraCharacteristics();
StreamConfigurationMap map = cc.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
assert map != null;
mImageDimension = map.getOutputSizes(SurfaceTexture.class)[0];
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(mImageDimension.getWidth(), mImageDimension.getHeight());
Surface surface = new Surface(texture);
mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mCaptureRequestBuilder.addTarget(surface);
mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
//The camera is already closed
if (null == mCameraDevice) {
return;
}
// When the session is ready, we start displaying the preview.
mCameraCaptureSessions = cameraCaptureSession;
updatePreview();
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Toast.makeText(mActivity, "Configuration change", Toast.LENGTH_SHORT).show();
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
protected void updatePreview() {
if (null == mCameraDevice) {
Log.e(TAG, "updatePreview error, return");
}
mCaptureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
try {
mCameraCaptureSessions.setRepeatingRequest(mCaptureRequestBuilder.build(), null, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
public void closeCamera() {
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
if (null != mImageReader) {
mImageReader.close();
mImageReader = null;
}
}
private static int sensorToDeviceRotation(CameraCharacteristics cameraCharacteristics, int deviceOrientation) {
int sensorOrienatation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
deviceOrientation = ORIENTATIONS.get(deviceOrientation);
return (sensorOrienatation + deviceOrientation + 360) % 360;
}
private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
int textureViewHeight) {
// Collect the supported resolutions that are at least as big as the preview Surface
List<Size> bigEnough = new ArrayList<>();
// Collect the supported resolutions that are smaller than the preview Surface
for (Size option : choices) {
if (option.getHeight() == option.getWidth() * textureViewHeight / textureViewWidth &&
option.getWidth() >= textureViewWidth &&
option.getHeight() >= textureViewHeight) {
bigEnough.add(option);
}
}
// Pick the smallest of those big enough. If there is no one big enough, pick the
// largest of those not big enough.
if (!bigEnough.isEmpty()) {
return Collections.min(bigEnough, new CompareSizeByArea());
} else {
Log.e(TAG, "Couldn't find any suitable preview size");
return choices[0];
}
}
private void createFolder() {
File folder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
mFileFolder = new File(folder, "Camera2TestApp");
if (!mFileFolder.exists()) {
mFileFolder.mkdirs();
}
}
public void resumeCamera() {
startBackgroundThread();
if (mTextureView.isAvailable()) {
setupCamera(mTextureView.getWidth(), mTextureView.getHeight());
connectCamera();
startPreview();
} else {
mTextureView.setSurfaceTextureListener(textureListener);
}
}
public void pauseCamera() {
closeCamera();
stopBackgroundThread();
}
public void destroyCamera() {
closeCamera();
}
}

View file

@ -1,9 +0,0 @@
package com.example.cameraxtestappjava.camera;
public interface CustomCameraCallback {
void onPictureTakenSuccess(String message);
void onPictureTakenFailError(String error);
}

View file

@ -0,0 +1,817 @@
package com.example.cameraxtestappjava.segpass;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.RectF;
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.Handler;
import android.os.HandlerThread;
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.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.ImageSaverUtil;
import com.example.cameraxtestappjava.segpass.camera.utils.ImageEncodingCallback;
import com.example.cameraxtestappjava.segpass.camera.utils.SegpassCameraCallback;
import com.example.cameraxtestappjava.segpass.camera.utils.SegpassPermissionCallback;
import com.example.cameraxtestappjava.segpass.camera.view.AutoFitTextureView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SegpassCamera {
/**
* Tag for the {@link Log}.
*/
private static final String TAG = "SegpassCamera";
/**
* Class variables
*/
AppCompatActivity mActivity;
SegpassPermissionCallback mPermissionListener;
SegpassCameraCallback mCameraCallback;
/**
* Conversion from screen rotation to JPEG orientation.
*/
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
public static final int REQUEST_CAMERA_PERMISSION = 200;
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);
}
/**
* Max preview width that is guaranteed by Camera2 API
*/
private static final int MAX_PREVIEW_WIDTH = 1920;
/**
* Max preview height that is guaranteed by Camera2 API
*/
private static final int MAX_PREVIEW_HEIGHT = 1080;
/**
* Error constants
*/
private static final String ERROR_MSG_CAMERA_ACCESS = "Error de acceso a cámara.";
private static final String ERROR_MSG_CAMERA_RUNTIME = "Error en ejecución de cámara.";
private static final String ERROR_MSG_CAMERA_CONFIG_FAILED = "Error en configuración de cámara.";
private static final String ERROR_MSG_CAMERA_TIMEOUT = "Tiempo de espera para bloqueo de cámara excedido.";
private static final String ERROR_MSG_NO_CAMERA_PREVIEW_SIZE = "No se encontraron tamaños de imagen compatible.";
private static final String ERROR_MSG_NO_CAMERA_CONFIGURATION_FOUND = "Error: no se encontró configuración de cámara";
private static final String ERROR_STATE_MSG_CAMERA_IN_USE = "Error: cámara en uso por otro proceso.";
private static final String ERROR_STATE_MSG_MAX_CAMERAS_IN_USE = "Error: máximo número de camaras en uso.";
private static final String ERROR_STATE_MSG_CAMERA_DISABLED = "Error: cámara inhabilitada.";
private static final String ERROR_STATE_MSG_CAMERA_DEVICE = "Error fatal en cámara. Intente reiniciar aplicación o dispositivo.";
private static final String ERROR_STATE_MSG_CAMERA_SERVICE = "Error fatal en cámara. Intente reiniciar dispositivo.";
/**
* ID of the current {@link CameraDevice}.
*/
private String mCameraId;
/**
* An {@link AutoFitTextureView} for camera preview.
*/
private final AutoFitTextureView mTextureView;
/**
* A {@link CameraCaptureSession } for camera preview.
*/
private CameraCaptureSession mCaptureSession;
/**
* A reference to the opened {@link CameraDevice}.
*/
private CameraDevice mCameraDevice;
/**
* The {@link android.util.Size} of camera preview.
*/
private Size mPreviewSize;
/**
* An additional thread for running tasks that shouldn't block the UI.
*/
private HandlerThread mBackgroundThread;
/**
* A {@link Handler} for running tasks in the background.
*/
private Handler mBackgroundHandler;
/**
* An {@link ImageReader} that handles still image capture.
*/
private ImageReader mImageReader;
/**
* This is the resulting image encoded to Base64
*/
private String mBase64Value;
/**
* The image file to show on the preview to accept or discard
*/
private Image mImage;
/**
* {@link CaptureRequest.Builder} for the camera preview
*/
private CaptureRequest.Builder mPreviewRequestBuilder;
/**
* {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder}
*/
private CaptureRequest mPreviewRequest;
/**
* A {@link Semaphore} to prevent the app from exiting before closing the camera.
*/
private final Semaphore mCameraOpenCloseLock = new Semaphore(1);
/**
* Orientation of the camera sensor
*/
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 SegpassPermissionCallback} that deals with camera and storage permissions.
* @param cameraCallback {@link SegpassCameraCallback} that deals with operation results.
*/
public SegpassCamera(AppCompatActivity activity, AutoFitTextureView textureView, SegpassPermissionCallback listener, SegpassCameraCallback cameraCallback) {
mActivity = activity;
mTextureView = textureView;
mPermissionListener = listener;
mCameraCallback = cameraCallback;
}
/**
* {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a
* {@link TextureView}.
*/
private final TextureView.SurfaceTextureListener mSurfaceTextureListener
= new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
openCamera(width, height);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
configureTransform(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
// No code implementation needed for this use case.
}
};
/**
* {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.
*/
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
// This method is called when the camera is opened. We start camera preview here.
mCameraOpenCloseLock.release();
mCameraDevice = cameraDevice;
createCameraPreviewSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
switch (error) {
case CameraDevice.StateCallback.ERROR_CAMERA_IN_USE:
mCameraCallback.onCameraInitError(ERROR_STATE_MSG_CAMERA_IN_USE);
break;
case CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE:
mCameraCallback.onCameraInitError(ERROR_STATE_MSG_MAX_CAMERAS_IN_USE);
break;
case CameraDevice.StateCallback.ERROR_CAMERA_DISABLED:
mCameraCallback.onCameraInitError(ERROR_STATE_MSG_CAMERA_DISABLED);
break;
case CameraDevice.StateCallback.ERROR_CAMERA_DEVICE:
mCameraCallback.onCameraInitError(ERROR_STATE_MSG_CAMERA_DEVICE);
break;
default:
mCameraCallback.onCameraInitError(ERROR_STATE_MSG_CAMERA_SERVICE);
break;
}
}
};
/**
* {@link ImageEncodingCallback} that deals with Base64 transformation.
*/
private final ImageEncodingCallback base64Callback = new ImageEncodingCallback() {
@Override
public void onImageEncodeSuccess(String base64) {
Log.d(TAG, "onBase64TransformationSuccess@base64Callback: image encoded successfully.");
mBase64Value = base64;
mCameraCallback.onPictureTakenSuccess(mBase64Value);
}
@Override
public void onImageEncodeFail(String error) {
Log.e(TAG, "onBase64TransformationSuccess@base64Callback" + error);
mBase64Value = null;
mCameraCallback.onPictureTakenFail(error);
}
};
/**
* This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
* still image is ready to be saved (or sent to be encoded to base64)
*/
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = reader -> mImage = reader.acquireNextImage();
/**
* Starts a background thread and its {@link Handler}.
*/
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
/**
* Stops the background thread and its {@link Handler}.
*/
private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
mBackgroundThread.interrupt();
Log.d(TAG, "InterruptedException@stopBackgroundThread(): " + e.getMessage());
}
}
/**
* Starts or resume a Camera. Must be invoked after {@link Activity} super.onResume().
*/
public void resumeCamera() {
startBackgroundThread();
// When the screen is turned off and turned back on, the SurfaceTexture is already
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
// a camera and start preview from here (otherwise, we wait until the surface is ready in
// the SurfaceTextureListener).
if (mTextureView.isAvailable()) {
openCamera(mTextureView.getWidth(), mTextureView.getHeight());
} else {
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
}
/**
* Pauses the camera (when the application leaves the activity or the app is sent to
* background. Must be invoked before {@link Activity} super.onPause().
*/
public void pauseCamera() {
closeCamera();
stopBackgroundThread();
}
/**
* Shows a {@link Toast} on the UI thread.
*
* @param text The message to show
*/
public void showToast(String text) {
mActivity.runOnUiThread(() -> Toast.makeText(mActivity, text, Toast.LENGTH_LONG).show());
}
/**
* Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that
* is at least as large as the respective texture view size, and that is at most as large as the
* respective max size, and whose aspect ratio matches with the specified value. If such size
* doesn't exist, choose the largest one that is at most as large as the respective max size,
* and whose aspect ratio matches with the specified value.
*
* @param choices The list of sizes that the camera supports for the intended output
* class
* @param textureViewWidth The width of the texture view relative to sensor coordinate
* @param textureViewHeight The height of the texture view relative to sensor coordinate
* @param maxWidth The maximum width that can be chosen
* @param maxHeight The maximum height that can be chosen
* @param aspectRatio The aspect ratio
* @return The optimal {@code Size}, or an arbitrary one if none were big enough
*/
private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
// Collect the supported resolutions that are at least as big as the preview Surface
List<Size> bigEnough = new ArrayList<>();
// Collect the supported resolutions that are smaller than the preview Surface
List<Size> notBigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices) {
if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
option.getHeight() == option.getWidth() * h / w) {
if (option.getWidth() >= textureViewWidth &&
option.getHeight() >= textureViewHeight) {
bigEnough.add(option);
} else {
notBigEnough.add(option);
}
}
}
// Pick the smallest of those big enough. If there is no one big enough, pick the
// largest of those not big enough.
if (!bigEnough.isEmpty()) {
return Collections.min(bigEnough, new CompareSizesByArea());
} else if (!notBigEnough.isEmpty()) {
return Collections.max(notBigEnough, new CompareSizesByArea());
} else {
Log.e(TAG, "Couldn't find any suitable preview size");
return choices[0];
}
}
/**
* Sets up member variables related to camera.
*
* @param width The width of available size for camera preview
* @param height The height of available size for camera preview
*/
private void setUpCameraOutputs(int width, int height) {
CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
try {
for (String cameraId : manager.getCameraIdList()) {
CameraCharacteristics characteristics
= manager.getCameraCharacteristics(cameraId);
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());
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(ERROR_MSG_CAMERA_ACCESS);
} catch (NullPointerException e) {
Log.e(TAG, "NullPointerException@setUpCameraOutputs(): " + e.getMessage());
mCameraCallback.onCameraInitError(ERROR_MSG_NO_CAMERA_CONFIGURATION_FOUND);
}
}
/**
* Validates whether the camera's current rotation is different from the output shown in the
* preview {@link AutoFitTextureView}
*
* @param characteristics Camera characteristics
* @return boolean value for display rotation
*/
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;
}
/**
* Obtains configuration map to be applied to the preview surface
*
* @param characteristics Camera characteristics
* @return the configuration map for the {@link AutoFitTextureView}
*/
@Nullable
private static StreamConfigurationMap getStreamConfigurationMap(CameraCharacteristics characteristics) {
// We don't use a back facing camera.
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
// If facing is null, return
if (facing == null) return null;
if (facing == CameraMetadata.LENS_FACING_BACK) return null;
return characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
}
/**
* Obtains optimal preview size to be shown in {@link AutoFitTextureView}
*
* @param width Current {@link AutoFitTextureView} width
* @param height Current {@link AutoFitTextureView} height
* @param swappedDimensions Whether the preview surface is rotate in relation to the previwe
* @param map Configuration map to be applies to the preview surface
* @param largest Largest display (i.e. image) {@link Size} supported by the preview.
*/
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);
}
/**
* Set image reader listeners
*
* @param largest Largest display (i.e. image) {@link Size} supported by the preview.
*/
private void setImageReaderListeners(Size largest) {
mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
ImageFormat.JPEG, /*maxImages*/2);
mImageReader.setOnImageAvailableListener(
mOnImageAvailableListener, mBackgroundHandler);
}
/**
* Sets the preview surface aspect ratio considering the screen orientation/
*/
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}.
*/
private void openCamera(int width, int height) {
// Check permissions
if (ContextCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
mPermissionListener.onPermissionRequest();
return;
}
setUpCameraOutputs(width, height);
configureTransform(width, height);
CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
try {
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new SegpassCameraException(ERROR_MSG_CAMERA_TIMEOUT);
}
manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "CameraAccessException@openCamera(): " + e.getMessage());
mCameraCallback.onCameraInitError(ERROR_MSG_CAMERA_ACCESS);
} catch (InterruptedException e) {
Log.e(TAG, "InterruptedException@openCamera(): " + e.getMessage());
mCameraCallback.onCameraInitError(ERROR_MSG_CAMERA_RUNTIME);
mBackgroundThread.interrupt();
}
}
/**
* Closes the current {@link CameraDevice}.
*/
private void closeCamera() {
try {
mCameraOpenCloseLock.acquire();
if (null != mCaptureSession) {
mCaptureSession.close();
mCaptureSession = null;
}
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
if (null != mImageReader) {
mImageReader.close();
mImageReader = null;
}
} catch (InterruptedException e) {
mBackgroundThread.interrupt();
Log.e(TAG, "InterruptedException@closeCamera(): " + e.getMessage());
} finally {
mCameraOpenCloseLock.release();
}
}
/**
* Creates a new {@link CameraCaptureSession} for camera preview.
*/
private void createCameraPreviewSession() {
try {
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
// We configure the size of default buffer to be the size of camera preview we want.
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
// This is the output Surface we need to start preview.
Surface surface = new Surface(texture);
// We set up a CaptureRequest.Builder with the output Surface.
mPreviewRequestBuilder
= mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(surface);
// Here, we create a CameraCaptureSession for camera preview.
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
// The camera is already closed
if (null == mCameraDevice) {
return;
}
// When the session is ready, we start displaying the preview.
mCaptureSession = cameraCaptureSession;
try {
// Finally, we start displaying the camera preview.
mPreviewRequest = mPreviewRequestBuilder.build();
mCaptureSession.setRepeatingRequest(mPreviewRequest,
null, mBackgroundHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "onConfigured(): " + e.getMessage());
mCameraCallback.onCameraInitError(ERROR_MSG_CAMERA_ACCESS);
}
}
@Override
public void onConfigureFailed(
@NonNull CameraCaptureSession cameraCaptureSession) {
mCameraCallback.onCameraInitError(ERROR_MSG_CAMERA_CONFIG_FAILED);
}
}, null
);
} catch (CameraAccessException e) {
Log.e(TAG, "createCameraPreviewSession(): " + e.getMessage());
mCameraCallback.onCameraInitError(ERROR_MSG_CAMERA_ACCESS);
}
}
/**
* Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`.
* This method should be called after the camera preview size is determined in
* setUpCameraOutputs and also the size of `mTextureView` is fixed.
*
* @param viewWidth The width of `mTextureView`
* @param viewHeight The height of `mTextureView`
*/
private void configureTransform(int viewWidth, int viewHeight) {
if (null == mTextureView || null == mPreviewSize) {
return;
}
int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max(
(float) viewHeight / mPreviewSize.getHeight(),
(float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90f * (rotation - 2), centerX, centerY);
} else if (Surface.ROTATION_180 == rotation) {
matrix.postRotate(180, centerX, centerY);
}
mTextureView.setTransform(matrix);
}
/**
* Initiate a still image capture.
*/
public void takePicture() {
// Validate that the camera is available
if (mCameraDevice == null) {
Log.e(TAG, "cameraDevice is null");
return;
}
try {
// Get camera manager and characteristics
CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId);
// Obtains list of image size supported by the selected camera sensor.
Size[] jpegSizes = Objects.requireNonNull(characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)).getOutputSizes(ImageFormat.JPEG);
if (jpegSizes == null) throw new NullPointerException(ERROR_MSG_NO_CAMERA_PREVIEW_SIZE);
// Default dimensions
int width = 640;
int height = 480;
if (jpegSizes.length > 0) {
width = jpegSizes[0].getWidth();
height = jpegSizes[0].getHeight();
}
// Set a new instance of ImageReader
ImageReader mCaptureImageReader = getCaptureImageReader(width, height);
// Set the output surfaces (from where the picture is taken)
List<Surface> outputSurfaces = new ArrayList<>(2);
outputSurfaces.add(mCaptureImageReader.getSurface());
outputSurfaces.add(new Surface(mTextureView.getSurfaceTexture()));
// Create the CaptiureBuilder
final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mCaptureImageReader.getSurface());
captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
// Picture orientation
int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
// Listens when the image is finally taken.
mCaptureImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
final CameraCaptureSession.CaptureCallback captureListener = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
// No code needed here, events are handled separately.
}
};
// Initialized the session to listen to a picture capture event.
mCameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
try {
session.capture(captureBuilder.build(), captureListener, mBackgroundHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "onConfigured@takePicture(): " + e.getMessage());
mCameraCallback.onCameraInitError(ERROR_MSG_CAMERA_ACCESS);
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
// No implementation needed here.
}
}, mBackgroundHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "CameraAccessException@takePicture(): " + e.getMessage());
mCameraCallback.onCameraInitError(ERROR_MSG_CAMERA_ACCESS);
}
}
public void savePicture() {
// Save the picture. Delegate later actions to the host application.
mBackgroundHandler.post(new ImageSaverUtil(mImage, base64Callback));
}
public void discardPicture() {
// Discard taken picture, resume camera preview session
mImage = null;
createCameraPreviewSession();
}
/**
* Gets the ImageReader for capturing a still picture.
*
* @param width Supported image width by surface and sensor.
* @param height Supported image height by surface and sensor.
* @return {@link ImageReader} object for the preview surface.
*/
@NonNull
private static ImageReader getCaptureImageReader(int width, int height) {
return ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
}
/**
* Obtains orientation that will be applied to the captured image.
*
* @param rotation Rotation angle.
* @return Orientation value.
*/
private int getOrientation(int rotation) {
// Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
// We have to take that into account and rotate JPEG properly.
// For devices with orientation of 90, we simply return our mapping from ORIENTATIONS.
// For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
}
}

View file

@ -0,0 +1,40 @@
package com.example.cameraxtestappjava.segpass.camera.dialogs;
import android.app.Dialog;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import com.example.cameraxtestappjava.segpass.camera.interfaces.SegpassDialogListener;
public class SegpassConfirmationDialogFragment extends DialogFragment {
private final SegpassDialogListener listener;
private final String message;
public SegpassConfirmationDialogFragment(String message, SegpassDialogListener listener) {
this.listener = listener;
this.message = message;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(requireActivity())
.setMessage(message)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
if (listener != null) {
listener.onOkClicked();
}
})
.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
if (listener != null) {
listener.onCancelClicked();
}
dismiss(); // Dismiss the dialog
})
.create();
}
}

View file

@ -0,0 +1,38 @@
package com.example.cameraxtestappjava.segpass.camera.dialogs;
import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import com.example.cameraxtestappjava.segpass.camera.utils.SegpassErrorDialogListener;
public class SegpassErrorDialogFragment extends DialogFragment {
private final String message;
private final SegpassErrorDialogListener listener;
public SegpassErrorDialogFragment(String message, SegpassErrorDialogListener listener) {
this.message = message;
this.listener = listener;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
assert activity != null;
return new AlertDialog.Builder(activity)
.setMessage(message)
.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
if (listener != null) {
listener.onErrorOkClick(); // Call listener if available
}
dismiss(); // Dismiss the dialog
})
.create();
}
}

View file

@ -0,0 +1,12 @@
package com.example.cameraxtestappjava.segpass.camera.exceptions;
public class SegpassCameraException extends RuntimeException {
public SegpassCameraException(String message) {
super(message);
}
public SegpassCameraException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,6 @@
package com.example.cameraxtestappjava.segpass.camera.interfaces;
public interface SegpassDialogListener {
void onOkClicked();
void onCancelClicked();
}

View file

@ -0,0 +1,19 @@
package com.example.cameraxtestappjava.segpass.camera.utils;
import android.util.Size;
import java.util.Comparator;
/**
* Compares two {@code Size}s based on their areas.
*/
public class CompareSizesByArea implements Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {
// We cast here to ensure the multiplications won't overflow
return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
(long) rhs.getWidth() * rhs.getHeight());
}
}

View file

@ -0,0 +1,8 @@
package com.example.cameraxtestappjava.segpass.camera.utils;
public interface ImageEncodingCallback {
void onImageEncodeSuccess(String base64);
void onImageEncodeFail(String error);
}

View file

@ -0,0 +1,69 @@
package com.example.cameraxtestappjava.segpass.camera.utils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.Image;
import android.util.Base64;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
public class ImageSaverUtil implements Runnable {
/**
* Tag for the {@link Log}
*/
private static final String TAG = "ImageSaverUtil";
/**
* The JPEG image
*/
private final Image mImage;
/**
* Callback to return encoded image value in {@link Base64}
*/
private final ImageEncodingCallback mCallback;
public ImageSaverUtil(Image image, ImageEncodingCallback callback) {
mImage = image;
mCallback = callback;
}
@Override
public void run() {
try {
// Get image as byte data buffer
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.capacity()];
buffer.get(bytes);
// Convert byte data to a Bitmap
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
// Create a ByteArrayOutputStream to capture the compressed image data
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// Compress the bitmap with JPEG format and desired quality (25% will do for this use case)
bitmap.compress(Bitmap.CompressFormat.JPEG, 25, outputStream);
// Get the compressed image data as a byte array
byte[] compressedImageData = outputStream.toByteArray();
// Encode the compressed data to Base64 string
String base64String = Base64.encodeToString(compressedImageData, Base64.NO_WRAP);
// Close the stream (optional, as ByteArrayOutputStream should close automatically)
outputStream.close();
// Return Base64 string on callback
mCallback.onImageEncodeSuccess(base64String);
} catch (Exception e) {
Log.e(TAG, "Error encoding photo: " + e.getMessage());
mCallback.onImageEncodeFail(e.getMessage());
}
}
}

View file

@ -0,0 +1,9 @@
package com.example.cameraxtestappjava.segpass.camera.utils;
public interface SegpassCameraCallback {
void onPictureTakenSuccess(String base64);
void onPictureTakenFail(String error);
void onCameraInitError(String errorMessage);
}

View file

@ -0,0 +1,5 @@
package com.example.cameraxtestappjava.segpass.camera.utils;
public interface SegpassErrorDialogListener {
void onErrorOkClick();
}

View file

@ -0,0 +1,7 @@
package com.example.cameraxtestappjava.segpass.camera.utils;
public interface SegpassPermissionCallback {
void onPermissionRequest();
void onPermissionGranted();
void onPermissionDenied();
}

View file

@ -0,0 +1,57 @@
package com.example.cameraxtestappjava.segpass.camera.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.TextureView;
public class AutoFitTextureView extends TextureView {
private int mRatioWidth = 0;
private int mRatioHeight = 0;
public AutoFitTextureView(Context context) {
this(context, null);
}
public AutoFitTextureView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
* calculated from the parameters. Note that the actual sizes of parameters don't matter, that
* is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
*
* @param width Relative horizontal size
* @param height Relative vertical size
*/
public void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
}

View file

@ -0,0 +1,139 @@
package com.example.cameraxtestappjava.segpass.camera.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.example.cameraxtestappjava.R;
import com.example.cameraxtestappjava.segpass.SegpassCamera;
import com.example.cameraxtestappjava.segpass.camera.utils.SegpassCameraCallback;
import com.example.cameraxtestappjava.segpass.camera.utils.SegpassPermissionCallback;
public class SegpassCameraLayout extends FrameLayout {
/**
* Parent {@link AppCompatActivity} where the SegpassCameraLayout is instantiated from.
*/
private AppCompatActivity mActivity;
/**
* Callback for camera permissions
*/
private SegpassPermissionCallback mPermissionListener;
/**
* Callback dealing with camera state and results.
*/
private SegpassCameraCallback mCameraCallback;
/**
* SegpassCamera
*/
private SegpassCamera mCamera;
/**
* SegpassCameraLayout root view.
*/
private final View rootView;
/**
*
*/
private ImageButton mTakePicture;
private ImageButton mDiscardPicture;
private ImageButton mSavePicture;
public SegpassCameraLayout(@NonNull Context context) {
super(context);
rootView = inflate(context, R.layout.segpass_camera_layout, this);
}
public SegpassCameraLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
rootView = inflate(context, R.layout.segpass_camera_layout, this);
}
/**
* @param activity Parent activity where the camera is called from.
* @param listener {@link SegpassPermissionCallback} that deals with camera and storage permissions.
* @param cameraCallback {@link SegpassCameraCallback} that deals with operation results.
*/
public void init(AppCompatActivity activity, SegpassPermissionCallback listener, SegpassCameraCallback cameraCallback) {
mActivity = activity;
mPermissionListener = listener;
mCameraCallback = cameraCallback;
setUpCamera();
setUpViews();
setUpListeners();
}
/**
* Set up {@link AutoFitTextureView} and {@link SegpassCamera}
*/
private void setUpCamera() {
AutoFitTextureView mTextureView = rootView.findViewById(R.id.tvCameraTextureView);
mCamera = new SegpassCamera(mActivity, mTextureView, mPermissionListener, mCameraCallback);
mCamera.resumeCamera();
}
private void setUpViews() {
mTakePicture = rootView.findViewById(R.id.btnTakePhoto);
mDiscardPicture = rootView.findViewById(R.id.btnDiscardPicture);
mSavePicture = rootView.findViewById(R.id.btnSavePicture);
}
private void setUpListeners() {
mTakePicture.setOnClickListener(v -> {
showPictureButtons();
hideShutterButton();
mCamera.takePicture();
});
mDiscardPicture.setOnClickListener(v -> {
showShutterButton();
hidePictureButtons();
mCamera.discardPicture();
});
mSavePicture.setOnClickListener(v -> {
showShutterButton();
hidePictureButtons();
mCamera.savePicture();
});
}
public void resumeCamera() {
mCamera.resumeCamera();
}
public void pauseCamera() {
mCamera.pauseCamera();
}
public void showToast(String message) {
mCamera.showToast(message);
}
private void showShutterButton() {
mTakePicture.setVisibility(View.VISIBLE);
}
private void hideShutterButton() {
mTakePicture.setVisibility(View.GONE);
}
private void showPictureButtons() {
mSavePicture.setVisibility(View.VISIBLE);
mDiscardPicture.setVisibility(View.VISIBLE);
}
private void hidePictureButtons() {
mSavePicture.setVisibility(View.GONE);
mDiscardPicture.setVisibility(View.GONE);
}
}

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:fillColor="#00FFFFFF"
android:pathData="M17,5L8,15l-5,-4"
android:strokeWidth="2"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#00FFFFFF"
android:pathData="M18,18L12,12M12,12L6,6M12,12L18,6M12,12L6,18"
android:strokeWidth="2"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CameraActivityOld">
<include
android:id="@+id/inCamera2"
layout="@layout/segpass_camera_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -7,10 +7,4 @@
android:layout_height="match_parent"
tools:context=".Camera2Activity">
<include
android:id="@+id/c2Camera"
layout="@layout/custom_camera2_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CameraActivityNew">
<include
android:id="@+id/inCamera2"
layout="@layout/segpass_camera_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

View file

@ -7,46 +7,14 @@
android:layout_height="match_parent"
tools:context=".MainActivity">
<include
android:id="@+id/c2Camera"
layout="@layout/custom_camera2_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include
android:visibility="gone"
android:id="@+id/cvCustomCamera"
layout="@layout/custom_camera_view"
android:layout_height="match_parent"
android:layout_width="match_parent" />
<!--<androidx.camera.view.PreviewView
android:id="@+id/pvViewfinder"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/llCameraButtons"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/llCameraButtons"
android:layout_width="match_parent"
<Button
android:id="@+id/goToCamera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/pvViewfinder">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnTakePicture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Take Picture" />
</LinearLayout>-->
app:layout_constraintEnd_toEndOf="parent"
android:text="OPEN CAMERA" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SegpassCameraActivity">
<com.example.cameraxtestappjava.segpass.camera.view.SegpassCameraLayout
android:id="@+id/sclCameraLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -10,59 +10,12 @@
<TextureView
android:id="@+id/tvCameraTextureView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/btn_takepicture"
android:layout_height="match_parent"
android:layout_above="@id/btnTakePhoto"
android:layout_alignParentTop="true" />
<RelativeLayout
android:id="@+id/rl1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:background="#66000000"
android:minHeight="200px">
<TextView
android:id="@+id/tv2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="Camera Ids:"
android:textColor="#ffffff" />
</RelativeLayout>
<ImageButton
android:id="@+id/sw_lens"
android:layout_width="50dip"
android:layout_height="50dip"
android:layout_above="@+id/rl2"
android:layout_centerHorizontal="true"
android:background="?android:selectableItemBackground"
android:contentDescription="switch_lens"
android:padding="10dip"
android:scaleType="fitCenter"
android:src="@drawable/capture_button_video"
android:text="1X" />
<RelativeLayout
android:id="@+id/rl2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#66000000"
android:minHeight="500px">
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="Camera Ids:"
android:textColor="#ffffff" />
</RelativeLayout>
<ImageButton
android:id="@+id/btn_takepicture"
android:id="@+id/btnTakePhoto"
android:layout_width="80dip"
android:layout_height="80dip"
android:layout_alignParentBottom="true"
@ -71,6 +24,6 @@
android:contentDescription="take_photo"
android:padding="10dip"
android:scaleType="fitCenter"
android:src="@drawable/capture_button_video" />
android:src="@drawable/capture_picture_button" />
</RelativeLayout>

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
android:orientation="vertical">
<com.example.cameraxtestappjava.segpass.camera.view.AutoFitTextureView
android:id="@+id/tvCameraTextureView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="@+id/btnTakePhoto"
android:layout_width="80dip"
android:layout_height="80dip"
android:layout_gravity="bottom|center"
android:background="?android:selectableItemBackground"
android:padding="10dp"
android:scaleType="fitCenter"
android:src="@drawable/capture_picture_button" />
<ImageButton
android:id="@+id/btnDiscardPicture"
android:layout_width="72dip"
android:layout_height="72dip"
android:layout_gravity="bottom|start"
android:background="?android:selectableItemBackground"
android:padding="15dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_close"
android:visibility="gone" />
<ImageButton
android:id="@+id/btnSavePicture"
android:layout_width="72dip"
android:layout_height="72dip"
android:layout_gravity="bottom|end"
android:background="?android:selectableItemBackground"
android:padding="15dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_check"
android:visibility="gone" />
</FrameLayout>

View file

@ -8,6 +8,7 @@ material = "1.12.0"
activity = "1.8.0"
constraintlayout = "2.1.4"
camerax = "1.3.3"
robolectric = "4.8.1"
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
@ -22,6 +23,7 @@ camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.r
camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camerax"}
camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "camerax"}
camera-extensions = { group = "androidx.camera", name = "camera-extensions", version.ref = "camerax"}
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }