Compare commits
23 commits
Author | SHA1 | Date | |
---|---|---|---|
fbe834b8e2 | |||
986cf8cf9a | |||
35a3f6cba3 | |||
1ad060a816 | |||
5f8cfd5a1f | |||
ebdff6b407 | |||
01bc596284 | |||
623c1bf95f | |||
fcde6328a5 | |||
09670b4a33 | |||
51bd224ce5 | |||
2f449a55c8 | |||
5aeeee0b44 | |||
11d14f2517 | |||
6fdb9f4f7b | |||
43415be913 | |||
84d1dcf058 | |||
711c362486 | |||
f254d77dd4 | |||
807a8619a8 | |||
022542cc72 | |||
674664e059 | |||
ee00263034 |
28 changed files with 1211 additions and 777 deletions
|
@ -48,4 +48,5 @@ dependencies {
|
||||||
testImplementation libs.junit
|
testImplementation libs.junit
|
||||||
androidTestImplementation libs.ext.junit
|
androidTestImplementation libs.ext.junit
|
||||||
androidTestImplementation libs.espresso.core
|
androidTestImplementation libs.espresso.core
|
||||||
|
testImplementation libs.robolectric
|
||||||
}
|
}
|
|
@ -7,7 +7,8 @@
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="32" />
|
android:maxSdkVersion="32"
|
||||||
|
tools:ignore="ScopedStorage" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
@ -19,6 +20,12 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.CameraXTestAppJava"
|
android:theme="@style/Theme.CameraXTestAppJava"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
|
<activity
|
||||||
|
android:name=".CameraActivityNew"
|
||||||
|
android:exported="false" />
|
||||||
|
<activity
|
||||||
|
android:name=".CameraActivityOld"
|
||||||
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".Camera2Activity"
|
android:name=".Camera2Activity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
|
@ -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.btnTakepicture.setOnClickListener(v -> mSegpassCamera.takePicture());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Camera callback
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPictureTakenSuccess(String base64) {
|
||||||
|
mSegpassCamera.showToast("Base64 generated.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPictureTakenFailError(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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.Intent;
|
||||||
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Bundle;
|
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.activity.EdgeToEdge;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
import androidx.core.graphics.Insets;
|
import androidx.core.graphics.Insets;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.core.view.WindowInsetsCompat;
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
|
|
||||||
import com.example.cameraxtestappjava.camera.CustomCamera2;
|
|
||||||
import com.example.cameraxtestappjava.camera.CustomCameraCallback;
|
|
||||||
import com.example.cameraxtestappjava.databinding.ActivityMainBinding;
|
import com.example.cameraxtestappjava.databinding.ActivityMainBinding;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
@ -28,7 +19,6 @@ public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
// Camera2
|
// Camera2
|
||||||
private static final String TAG = "MainActivity";
|
private static final String TAG = "MainActivity";
|
||||||
CustomCamera2 mCustomCamera2;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
@ -38,7 +28,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
|
|
||||||
setUpCamera2();
|
setListeners();
|
||||||
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
||||||
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||||
|
@ -47,61 +37,17 @@ public class MainActivity extends AppCompatActivity {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Camera 2
|
private void setListeners() {
|
||||||
private void setUpCamera2() {
|
binding.goToCamera.setOnClickListener(v -> {
|
||||||
|
Intent intent = new Intent(this, CameraActivityNew.class);
|
||||||
// Initialize visual elements in host app
|
startActivity(intent);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
Log.d(TAG, "onResume");
|
setListeners();
|
||||||
mCustomCamera2.resumeCamera();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
Log.d(TAG, "onPause");
|
|
||||||
mCustomCamera2.pauseCamera();
|
|
||||||
super.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
Log.d(TAG, "onPause");
|
|
||||||
mCustomCamera2.destroyCamera();
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package com.example.cameraxtestappjava.camera;
|
|
||||||
|
|
||||||
public interface CustomCameraCallback {
|
|
||||||
|
|
||||||
void onPictureTakenSuccess(String message);
|
|
||||||
void onPictureTakenFailError(String error);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,732 @@
|
||||||
|
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.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.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class SegpassCamera {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag for the {@link Log}.
|
||||||
|
*/
|
||||||
|
private static final String TAG = "SegpassCamera";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class variabels
|
||||||
|
*/
|
||||||
|
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_CAMERA_ACCESS = "Camera access error.";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@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;
|
||||||
|
mActivity.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImageEncodeFail(String error) {
|
||||||
|
Log.e(TAG, "onBase64TransformationSuccess@base64Callback" + error);
|
||||||
|
mBase64Value = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 ->
|
||||||
|
mBackgroundHandler.post(new ImageSaverUtil(reader.acquireNextImage(), base64Callback));
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(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();
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
|
||||||
|
boolean swappedDimensions = false;
|
||||||
|
switch (displayRotation) {
|
||||||
|
case Surface.ROTATION_0:
|
||||||
|
case Surface.ROTATION_180:
|
||||||
|
if (mSensorOrientation == 90 || mSensorOrientation == 270) {
|
||||||
|
swappedDimensions = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Surface.ROTATION_90:
|
||||||
|
case Surface.ROTATION_270:
|
||||||
|
if (mSensorOrientation == 0 || mSensorOrientation == 180) {
|
||||||
|
swappedDimensions = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.e(TAG, "Display rotation is invalid: " + displayRotation);
|
||||||
|
}
|
||||||
|
return swappedDimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static StreamConfigurationMap getStreamConfigurationMap(CameraCharacteristics characteristics) {
|
||||||
|
// We don't use a back facing camera.
|
||||||
|
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
|
||||||
|
|
||||||
|
// If facis is null, return
|
||||||
|
if (facing == null) return null;
|
||||||
|
if (facing == CameraMetadata.LENS_FACING_BACK) return null;
|
||||||
|
|
||||||
|
return characteristics.get(
|
||||||
|
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getOptimalPreviewSize(int width, int height, boolean swappedDimensions, StreamConfigurationMap map, Size largest) {
|
||||||
|
Point displaySize = new Point();
|
||||||
|
mActivity.getWindowManager().getDefaultDisplay().getSize(displaySize);
|
||||||
|
int rotatedPreviewWidth = width;
|
||||||
|
int rotatedPreviewHeight = height;
|
||||||
|
int maxPreviewWidth = displaySize.x;
|
||||||
|
int maxPreviewHeight = displaySize.y;
|
||||||
|
|
||||||
|
if (swappedDimensions) {
|
||||||
|
rotatedPreviewWidth = height;
|
||||||
|
rotatedPreviewHeight = width;
|
||||||
|
maxPreviewWidth = displaySize.y;
|
||||||
|
maxPreviewHeight = displaySize.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
|
||||||
|
maxPreviewWidth = MAX_PREVIEW_WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
|
||||||
|
maxPreviewHeight = MAX_PREVIEW_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Danger, W.R.! Attempting to use too large a preview size could exceed the camera
|
||||||
|
// bus' bandwidth limitation, resulting in gorgeous previews but the storage of
|
||||||
|
// garbage capture data.
|
||||||
|
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
|
||||||
|
rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
|
||||||
|
maxPreviewHeight, largest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setImageReaderListeners(Size largest) {
|
||||||
|
mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
|
||||||
|
ImageFormat.JPEG, /*maxImages*/2);
|
||||||
|
mImageReader.setOnImageAvailableListener(
|
||||||
|
mOnImageAvailableListener, mBackgroundHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPreviewAspectRatio() {
|
||||||
|
// We fit the aspect ratio of TextureView to the size of preview we picked.
|
||||||
|
int orientation = mActivity.getResources().getConfiguration().orientation;
|
||||||
|
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
mTextureView.setAspectRatio(
|
||||||
|
mPreviewSize.getWidth(), mPreviewSize.getHeight());
|
||||||
|
} else {
|
||||||
|
mTextureView.setAspectRatio(
|
||||||
|
mPreviewSize.getHeight(), mPreviewSize.getWidth());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the camera specified by {@link SegpassCamera#mCameraId}.
|
||||||
|
*/
|
||||||
|
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("Time out waiting to lock camera opening.");
|
||||||
|
}
|
||||||
|
manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
|
||||||
|
} catch (CameraAccessException e) {
|
||||||
|
Log.e(TAG, "CameraAccessException@openCamera(): " + e.getMessage());
|
||||||
|
mCameraCallback.onCameraInitError(e.getMessage());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
mBackgroundThread.interrupt();
|
||||||
|
Log.e(TAG, "InterruptedException@openCamera(): " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigureFailed(
|
||||||
|
@NonNull CameraCaptureSession cameraCaptureSession) {
|
||||||
|
mCameraCallback.onCameraInitError("Camera capture session failed.");
|
||||||
|
}
|
||||||
|
}, null
|
||||||
|
);
|
||||||
|
} catch (CameraAccessException e) {
|
||||||
|
Log.e(TAG, "createCameraPreviewSession(): " + e.getMessage());
|
||||||
|
mCameraCallback.onCameraInitError(ERROR_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);
|
||||||
|
|
||||||
|
//
|
||||||
|
Size[] jpegSizes = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).getOutputSizes(ImageFormat.JPEG);
|
||||||
|
if (jpegSizes == null) throw new NullPointerException("Error taking picture");
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
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);
|
||||||
|
if (mBase64Value != null) {
|
||||||
|
Log.i(TAG, "onCaptureCompleted@takePicture(): sending Base64 result on callback... ");
|
||||||
|
mCameraCallback.onPictureTakenSuccess(mBase64Value);
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "onCaptureCompleted@takePicture(): error saving picture, base64 value is null.");
|
||||||
|
mCameraCallback.onPictureTakenFailError("Error saving picture...");
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Recreating camera preview...");
|
||||||
|
createCameraPreviewSession();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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_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_CAMERA_ACCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static ImageReader getCaptureImageReader(int width, int height) {
|
||||||
|
return ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.example.cameraxtestappjava.segpass.camera.interfaces;
|
||||||
|
|
||||||
|
public interface SegpassDialogListener {
|
||||||
|
void onOkClicked();
|
||||||
|
void onCancelClicked();
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.example.cameraxtestappjava.segpass.camera.utils;
|
||||||
|
|
||||||
|
public interface ImageEncodingCallback {
|
||||||
|
void onImageEncodeSuccess(String base64);
|
||||||
|
|
||||||
|
void onImageEncodeFail(String error);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.example.cameraxtestappjava.segpass.camera.utils;
|
||||||
|
|
||||||
|
import android.media.Image;
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
public class ImageSaverUtil implements Runnable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The JPEG image
|
||||||
|
*/
|
||||||
|
private final Image mImage;
|
||||||
|
private final ImageEncodingCallback mCallback;
|
||||||
|
|
||||||
|
public ImageSaverUtil(Image image, ImageEncodingCallback callback) {
|
||||||
|
mImage = image;
|
||||||
|
mCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
|
||||||
|
byte[] bytes = new byte[buffer.capacity()];
|
||||||
|
buffer.get(bytes);
|
||||||
|
mCallback.onImageEncodeSuccess(Base64.encodeToString(bytes, Base64.NO_WRAP));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
mCallback.onImageEncodeFail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.example.cameraxtestappjava.segpass.camera.utils;
|
||||||
|
|
||||||
|
public interface SegpassCameraCallback {
|
||||||
|
|
||||||
|
void onPictureTakenSuccess(String base64);
|
||||||
|
void onPictureTakenFailError(String error);
|
||||||
|
void onCameraInitError(String errorMessage);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.example.cameraxtestappjava.segpass.camera.utils;
|
||||||
|
|
||||||
|
public interface SegpassErrorDialogListener {
|
||||||
|
void onErrorOkClick();
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.example.cameraxtestappjava.segpass.camera.utils;
|
||||||
|
|
||||||
|
public interface SegpassPermissionCallback {
|
||||||
|
void onPermissionRequest();
|
||||||
|
void onPermissionGranted();
|
||||||
|
void onPermissionDenied();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
app/src/main/res/layout/activity_camera.xml
Normal file
16
app/src/main/res/layout/activity_camera.xml
Normal 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/camera_autofit_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -7,10 +7,4 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".Camera2Activity">
|
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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
16
app/src/main/res/layout/activity_camera_new.xml
Normal file
16
app/src/main/res/layout/activity_camera_new.xml
Normal 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/camera_autofit_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
|
@ -7,46 +7,14 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".MainActivity">
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
<include
|
<Button
|
||||||
android:id="@+id/c2Camera"
|
android:id="@+id/goToCamera"
|
||||||
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"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
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_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Take Picture" />
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
</LinearLayout>-->
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:text="OPEN CAMERA" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
26
app/src/main/res/layout/camera_autofit_view.xml
Normal file
26
app/src/main/res/layout/camera_autofit_view.xml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#000000"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<com.example.cameraxtestappjava.segpass.camera.view.AutoFitTextureView
|
||||||
|
android:id="@+id/tvCameraTextureView"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_takepicture"
|
||||||
|
android:layout_width="80dip"
|
||||||
|
android:layout_height="80dip"
|
||||||
|
android:background="?android:selectableItemBackground"
|
||||||
|
android:padding="10dip"
|
||||||
|
android:layout_gravity="bottom|center"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/capture_button_video" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
|
@ -10,57 +10,10 @@
|
||||||
<TextureView
|
<TextureView
|
||||||
android:id="@+id/tvCameraTextureView"
|
android:id="@+id/tvCameraTextureView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_above="@+id/btn_takepicture"
|
android:layout_above="@id/btn_takepicture"
|
||||||
android:layout_alignParentTop="true" />
|
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
|
<ImageButton
|
||||||
android:id="@+id/btn_takepicture"
|
android:id="@+id/btn_takepicture"
|
||||||
android:layout_width="80dip"
|
android:layout_width="80dip"
|
||||||
|
|
|
@ -8,6 +8,7 @@ material = "1.12.0"
|
||||||
activity = "1.8.0"
|
activity = "1.8.0"
|
||||||
constraintlayout = "2.1.4"
|
constraintlayout = "2.1.4"
|
||||||
camerax = "1.3.3"
|
camerax = "1.3.3"
|
||||||
|
robolectric = "4.8.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
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-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camerax"}
|
||||||
camera-view = { group = "androidx.camera", name = "camera-view", 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"}
|
camera-extensions = { group = "androidx.camera", name = "camera-extensions", version.ref = "camerax"}
|
||||||
|
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
Loading…
Reference in a new issue