|
|
|
@ -8,7 +8,6 @@ import android.hardware.camera2.CameraManager
|
|
|
|
|
import android.os.Build
|
|
|
|
|
import android.os.Bundle
|
|
|
|
|
import android.text.InputType
|
|
|
|
|
import android.util.DisplayMetrics
|
|
|
|
|
import android.util.Size
|
|
|
|
|
import android.view.MotionEvent
|
|
|
|
|
import android.view.ScaleGestureDetector
|
|
|
|
@ -23,7 +22,8 @@ import android.widget.RelativeLayout
|
|
|
|
|
import android.widget.Toast
|
|
|
|
|
import androidx.camera.camera2.interop.Camera2CameraInfo
|
|
|
|
|
import androidx.camera.core.*
|
|
|
|
|
import androidx.camera.extensions.HdrImageCaptureExtender
|
|
|
|
|
import androidx.camera.extensions.ExtensionMode
|
|
|
|
|
import androidx.camera.extensions.ExtensionsManager
|
|
|
|
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
|
|
|
|
import androidx.core.content.ContextCompat
|
|
|
|
|
import sushi.hardcore.droidfs.adapters.DialogSingleChoiceAdapter
|
|
|
|
@ -35,8 +35,7 @@ import java.io.ByteArrayInputStream
|
|
|
|
|
import java.io.ByteArrayOutputStream
|
|
|
|
|
import java.text.SimpleDateFormat
|
|
|
|
|
import java.util.*
|
|
|
|
|
import java.util.concurrent.ExecutorService
|
|
|
|
|
import java.util.concurrent.Executors
|
|
|
|
|
import java.util.concurrent.Executor
|
|
|
|
|
|
|
|
|
|
class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|
|
|
|
companion object {
|
|
|
|
@ -64,10 +63,16 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|
|
|
|
private lateinit var outputDirectory: String
|
|
|
|
|
private lateinit var fileName: String
|
|
|
|
|
private var isFinishingIntentionally = false
|
|
|
|
|
private lateinit var cameraExecutor: ExecutorService
|
|
|
|
|
private var permissionsGranted = false
|
|
|
|
|
private lateinit var executor: Executor
|
|
|
|
|
private lateinit var cameraProvider: ProcessCameraProvider
|
|
|
|
|
private lateinit var extensionsManager: ExtensionsManager
|
|
|
|
|
private val cameraPreview = Preview.Builder().build()
|
|
|
|
|
private var imageCapture: ImageCapture? = null
|
|
|
|
|
private var resolutions: Array<Size>? = null
|
|
|
|
|
private var camera: Camera? = null
|
|
|
|
|
private var resolutions: List<Size>? = null
|
|
|
|
|
private var currentResolutionIndex: Int = 0
|
|
|
|
|
private var captureMode = ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
|
|
|
|
|
private var isBackCamera = true
|
|
|
|
|
private lateinit var binding: ActivityCameraBinding
|
|
|
|
|
|
|
|
|
@ -81,21 +86,61 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
|
|
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED){
|
|
|
|
|
setupCamera()
|
|
|
|
|
permissionsGranted = true
|
|
|
|
|
} else {
|
|
|
|
|
requestPermissions(arrayOf(Manifest.permission.CAMERA), CAMERA_PERMISSION_REQUEST_CODE)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
setupCamera()
|
|
|
|
|
permissionsGranted = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cameraExecutor = Executors.newSingleThreadExecutor()
|
|
|
|
|
executor = ContextCompat.getMainExecutor(this)
|
|
|
|
|
cameraPreview.setSurfaceProvider(binding.cameraPreview.surfaceProvider)
|
|
|
|
|
ProcessCameraProvider.getInstance(this).apply {
|
|
|
|
|
addListener({
|
|
|
|
|
cameraProvider = get()
|
|
|
|
|
setupCamera()
|
|
|
|
|
}, executor)
|
|
|
|
|
}
|
|
|
|
|
ExtensionsManager.getInstance(this).apply {
|
|
|
|
|
addListener({
|
|
|
|
|
extensionsManager = get()
|
|
|
|
|
setupCamera()
|
|
|
|
|
}, executor)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
binding.imageCaptureMode.setOnClickListener {
|
|
|
|
|
val currentIndex = if (captureMode == ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) {
|
|
|
|
|
0
|
|
|
|
|
} else {
|
|
|
|
|
1
|
|
|
|
|
}
|
|
|
|
|
ColoredAlertDialogBuilder(this)
|
|
|
|
|
.setTitle(R.string.camera_optimization)
|
|
|
|
|
.setSingleChoiceItems(DialogSingleChoiceAdapter(this, arrayOf(R.string.maximize_quality, R.string.minimize_latency).map { getString(it) }), currentIndex) { dialog, which ->
|
|
|
|
|
val resId: Int
|
|
|
|
|
val newCaptureMode = if (which == 0) {
|
|
|
|
|
resId = R.drawable.icon_high_quality
|
|
|
|
|
ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
|
|
|
|
|
} else {
|
|
|
|
|
resId = R.drawable.icon_speed
|
|
|
|
|
ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY
|
|
|
|
|
}
|
|
|
|
|
if (newCaptureMode != captureMode) {
|
|
|
|
|
captureMode = newCaptureMode
|
|
|
|
|
binding.imageCaptureMode.setImageResource(resId)
|
|
|
|
|
setupCamera()
|
|
|
|
|
}
|
|
|
|
|
dialog.dismiss()
|
|
|
|
|
}
|
|
|
|
|
.setNegativeButton(R.string.cancel, null)
|
|
|
|
|
.show()
|
|
|
|
|
}
|
|
|
|
|
binding.imageRatio.setOnClickListener {
|
|
|
|
|
resolutions?.let {
|
|
|
|
|
ColoredAlertDialogBuilder(this)
|
|
|
|
|
.setTitle(R.string.choose_resolution)
|
|
|
|
|
.setSingleChoiceItems(DialogSingleChoiceAdapter(this, it.map { size -> size.toString() }.toTypedArray()), currentResolutionIndex) { dialog, which ->
|
|
|
|
|
.setSingleChoiceItems(DialogSingleChoiceAdapter(this, it.map { size -> size.toString() }), currentResolutionIndex) { dialog, which ->
|
|
|
|
|
setupCamera(resolutions!![which])
|
|
|
|
|
dialog.dismiss()
|
|
|
|
|
currentResolutionIndex = which
|
|
|
|
@ -151,10 +196,10 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|
|
|
|
}
|
|
|
|
|
binding.imageCameraSwitch.setOnClickListener {
|
|
|
|
|
isBackCamera = if (isBackCamera) {
|
|
|
|
|
binding.imageCameraSwitch.setImageResource(R.drawable.icon_camera_front)
|
|
|
|
|
binding.imageCameraSwitch.setImageResource(R.drawable.icon_camera_back)
|
|
|
|
|
false
|
|
|
|
|
} else {
|
|
|
|
|
binding.imageCameraSwitch.setImageResource(R.drawable.icon_camera_back)
|
|
|
|
|
binding.imageCameraSwitch.setImageResource(R.drawable.icon_camera_front)
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
setupCamera()
|
|
|
|
@ -165,8 +210,8 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|
|
|
|
|
|
|
|
|
val scaleGestureDetector = ScaleGestureDetector(this, object : ScaleGestureDetector.SimpleOnScaleGestureListener(){
|
|
|
|
|
override fun onScale(detector: ScaleGestureDetector): Boolean {
|
|
|
|
|
val currentZoomRatio = imageCapture?.camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: 0F
|
|
|
|
|
imageCapture?.camera?.cameraControl?.setZoomRatio(currentZoomRatio*detector.scaleFactor)
|
|
|
|
|
val currentZoomRatio = camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: 0F
|
|
|
|
|
camera?.cameraControl?.setZoomRatio(currentZoomRatio*detector.scaleFactor)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
@ -178,7 +223,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|
|
|
|
val factory = binding.cameraPreview.meteringPointFactory
|
|
|
|
|
val point = factory.createPoint(event.x, event.y)
|
|
|
|
|
val action = FocusMeteringAction.Builder(point).build()
|
|
|
|
|
imageCapture?.camera?.cameraControl?.startFocusAndMetering(action)
|
|
|
|
|
camera?.cameraControl?.startFocusAndMetering(action)
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
MotionEvent.ACTION_MOVE -> scaleGestureDetector.onTouchEvent(event)
|
|
|
|
@ -191,7 +236,10 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|
|
|
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
|
|
|
when (requestCode) {
|
|
|
|
|
CAMERA_PERMISSION_REQUEST_CODE -> if (grantResults.size == 1) {
|
|
|
|
|
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
|
|
|
|
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
|
|
|
permissionsGranted = true
|
|
|
|
|
setupCamera()
|
|
|
|
|
} else {
|
|
|
|
|
ColoredAlertDialogBuilder(this)
|
|
|
|
|
.setTitle(R.string.error)
|
|
|
|
|
.setMessage(R.string.camera_perm_needed)
|
|
|
|
@ -200,72 +248,62 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|
|
|
|
isFinishingIntentionally = true
|
|
|
|
|
finish()
|
|
|
|
|
}.show()
|
|
|
|
|
} else {
|
|
|
|
|
setupCamera()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun adaptPreviewSize(resolution: Size) {
|
|
|
|
|
val metrics = DisplayMetrics()
|
|
|
|
|
windowManager.defaultDisplay.getMetrics(metrics)
|
|
|
|
|
//resolution.width and resolution.height seem to be inverted
|
|
|
|
|
val width = resolution.height
|
|
|
|
|
val height = resolution.width
|
|
|
|
|
binding.cameraPreview.layoutParams = if (metrics.widthPixels < width){
|
|
|
|
|
val screenWidth = resources.displayMetrics.widthPixels
|
|
|
|
|
binding.cameraPreview.layoutParams = if (screenWidth < resolution.width) {
|
|
|
|
|
RelativeLayout.LayoutParams(
|
|
|
|
|
metrics.widthPixels,
|
|
|
|
|
(height * (metrics.widthPixels.toFloat() / width)).toInt()
|
|
|
|
|
screenWidth,
|
|
|
|
|
(resolution.height * (screenWidth.toFloat() / resolution.width)).toInt()
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
RelativeLayout.LayoutParams(width, height)
|
|
|
|
|
RelativeLayout.LayoutParams(resolution.width, resolution.height)
|
|
|
|
|
}
|
|
|
|
|
(binding.cameraPreview.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.CENTER_IN_PARENT)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun setupCamera(resolution: Size? = null){
|
|
|
|
|
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
|
|
|
|
cameraProviderFuture.addListener({
|
|
|
|
|
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
|
|
|
|
|
val preview = Preview.Builder()
|
|
|
|
|
.build()
|
|
|
|
|
.also {
|
|
|
|
|
it.setSurfaceProvider(binding.cameraPreview.surfaceProvider)
|
|
|
|
|
if (permissionsGranted && ::extensionsManager.isInitialized && ::cameraProvider.isInitialized) {
|
|
|
|
|
imageCapture = ImageCapture.Builder()
|
|
|
|
|
.setCaptureMode(captureMode)
|
|
|
|
|
.setFlashMode(imageCapture?.flashMode ?: ImageCapture.FLASH_MODE_AUTO)
|
|
|
|
|
.apply {
|
|
|
|
|
resolution?.let {
|
|
|
|
|
setTargetResolution(it)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
val builder = ImageCapture.Builder()
|
|
|
|
|
.setFlashMode(ImageCapture.FLASH_MODE_AUTO)
|
|
|
|
|
resolution?.let {
|
|
|
|
|
builder.setTargetResolution(it)
|
|
|
|
|
}
|
|
|
|
|
val hdrImageCapture = HdrImageCaptureExtender.create(builder)
|
|
|
|
|
val cameraSelector = if (isBackCamera){ CameraSelector.DEFAULT_BACK_CAMERA } else { CameraSelector.DEFAULT_FRONT_CAMERA }
|
|
|
|
|
.build()
|
|
|
|
|
|
|
|
|
|
if (hdrImageCapture.isExtensionAvailable(cameraSelector)){
|
|
|
|
|
hdrImageCapture.enableExtension(cameraSelector)
|
|
|
|
|
var cameraSelector = if (isBackCamera){ CameraSelector.DEFAULT_BACK_CAMERA } else { CameraSelector.DEFAULT_FRONT_CAMERA }
|
|
|
|
|
if (extensionsManager.isExtensionAvailable(cameraProvider, cameraSelector, ExtensionMode.HDR)) {
|
|
|
|
|
cameraSelector = extensionsManager.getExtensionEnabledCameraSelector(cameraProvider, cameraSelector, ExtensionMode.HDR)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
imageCapture = builder.build()
|
|
|
|
|
|
|
|
|
|
cameraProvider.unbindAll()
|
|
|
|
|
val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
|
|
|
|
|
camera = cameraProvider.bindToLifecycle(this, cameraSelector, cameraPreview, imageCapture)
|
|
|
|
|
|
|
|
|
|
adaptPreviewSize(imageCapture!!.attachedSurfaceResolution!!)
|
|
|
|
|
adaptPreviewSize(resolution ?: imageCapture!!.attachedSurfaceResolution!!.swap())
|
|
|
|
|
|
|
|
|
|
val info = Camera2CameraInfo.from(camera.cameraInfo)
|
|
|
|
|
val cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
|
|
|
|
val characteristics = cameraManager.getCameraCharacteristics(info.cameraId)
|
|
|
|
|
characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)?.let { streamConfigurationMap ->
|
|
|
|
|
resolutions = streamConfigurationMap.getOutputSizes(imageCapture!!.imageFormat)
|
|
|
|
|
if (resolutions == null) {
|
|
|
|
|
val info = Camera2CameraInfo.from(camera!!.cameraInfo)
|
|
|
|
|
val cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
|
|
|
|
val characteristics = cameraManager.getCameraCharacteristics(info.cameraId)
|
|
|
|
|
characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)?.let { streamConfigurationMap ->
|
|
|
|
|
resolutions = streamConfigurationMap.getOutputSizes(imageCapture!!.imageFormat).map { it.swap() }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, ContextCompat.getMainExecutor(this))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun takePhoto() {
|
|
|
|
|
val imageCapture = imageCapture ?: return
|
|
|
|
|
val outputBuff = ByteArrayOutputStream()
|
|
|
|
|
val outputOptions = ImageCapture.OutputFileOptions.Builder(outputBuff).build()
|
|
|
|
|
imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
|
|
|
|
|
imageCapture.takePicture(outputOptions, executor, object : ImageCapture.OnImageSavedCallback {
|
|
|
|
|
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
|
|
|
|
binding.takePhotoButton.onPhotoTaken()
|
|
|
|
|
if (gocryptfsVolume.importFile(ByteArrayInputStream(outputBuff.toByteArray()), PathUtils.pathJoin(outputDirectory, fileName))){
|
|
|
|
@ -313,7 +351,6 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|
|
|
|
|
|
|
|
|
override fun onDestroy() {
|
|
|
|
|
super.onDestroy()
|
|
|
|
|
cameraExecutor.shutdown()
|
|
|
|
|
if (!isFinishingIntentionally) {
|
|
|
|
|
gocryptfsVolume.close()
|
|
|
|
|
RestrictedFileProvider.wipeAll(this)
|
|
|
|
@ -365,4 +402,8 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
|
|
|
|
|
orientedIcons.map { it.startAnimation(rotateAnimation) }
|
|
|
|
|
previousOrientation = reversedOrientation
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Size.swap(): Size {
|
|
|
|
|
return Size(height, width)
|
|
|
|
|
}
|