From 26f33e8c2647d9c7e7a3fbe988b7bb6c6c5ea88c Mon Sep 17 00:00:00 2001 From: tibbi Date: Mon, 28 May 2018 21:47:23 +0200 Subject: [PATCH] add some initial PreviewCameraTwo implementation from the sample code --- .../camera/activities/MainActivity.kt | 4 +- .../camera/helpers/Constants.kt | 3 + .../camera/interfaces/MyPreview.kt | 4 + .../camera/views/PreviewCameraOne.kt | 4 + .../camera/views/PreviewCameraTwo.kt | 397 +++++++++++++++++- 5 files changed, 408 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt index 7d968d31..634ae903 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt @@ -84,6 +84,7 @@ class MainActivity : SimpleActivity(), PreviewListener, PhotoProcessor.MediaSave if (hasStorageAndCameraPermissions()) { mOrientationEventListener.enable() } + mPreview?.onResumed() } override fun onPause() { @@ -102,6 +103,7 @@ class MainActivity : SimpleActivity(), PreviewListener, PhotoProcessor.MediaSave if (mPreview?.getCameraState() == STATE_PICTURE_TAKEN) { toast(R.string.photo_not_saved) } + mPreview?.onPaused() } override fun onDestroy() { @@ -194,7 +196,7 @@ class MainActivity : SimpleActivity(), PreviewListener, PhotoProcessor.MediaSave (btn_holder.layoutParams as RelativeLayout.LayoutParams).setMargins(0, 0, 0, (navBarHeight + resources.getDimension(R.dimen.activity_margin)).toInt()) - mPreview = if (isLollipopPlus()) PreviewCameraTwo(this) else PreviewCameraOne(this, camera_surface_view, this) + mPreview = if (isLollipopPlus()) PreviewCameraTwo(this, camera_texture_view) else PreviewCameraOne(this, camera_surface_view, this) view_holder.addView(mPreview as ViewGroup) val imageDrawable = if (config.lastUsedCamera == mCameraImpl.getBackCameraId()) R.drawable.ic_camera_front else R.drawable.ic_camera_rear diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Constants.kt b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Constants.kt index f6c62f42..c281bf0c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Constants.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Constants.kt @@ -31,3 +31,6 @@ const val FLASH_AUTO = 2 // camera states const val STATE_PREVIEW = 0 const val STATE_PICTURE_TAKEN = 1 +const val STATE_WAITING_LOCK = 2 +const val STATE_WAITING_PRECAPTURE = 3 +const val STATE_WAITING_NON_PRECAPTURE = 4 diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt b/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt index 7f6e110e..dd9f4fcb 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt @@ -3,6 +3,10 @@ package com.simplemobiletools.camera.interfaces import android.net.Uri interface MyPreview { + fun onResumed() + + fun onPaused() + fun setTargetUri(uri: Uri) fun setIsImageCaptureIntent(isImageCaptureIntent: Boolean) diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/views/PreviewCameraOne.kt b/app/src/main/kotlin/com/simplemobiletools/camera/views/PreviewCameraOne.kt index 1193fddb..2d21efcb 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/views/PreviewCameraOne.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/views/PreviewCameraOne.kt @@ -113,6 +113,10 @@ class PreviewCameraOne : ViewGroup, SurfaceHolder.Callback, MyPreview { } } + override fun onResumed() {} + + override fun onPaused() {} + override fun tryInitVideoMode() { if (mIsSurfaceCreated) { initVideoMode() diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/views/PreviewCameraTwo.kt b/app/src/main/kotlin/com/simplemobiletools/camera/views/PreviewCameraTwo.kt index 32fa001f..4611a7da 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/views/PreviewCameraTwo.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/views/PreviewCameraTwo.kt @@ -1,14 +1,404 @@ package com.simplemobiletools.camera.views +import android.annotation.TargetApi import android.content.Context +import android.content.res.Configuration +import android.graphics.* +import android.hardware.camera2.* +import android.media.ImageReader import android.net.Uri +import android.os.Build +import android.os.Handler +import android.os.HandlerThread +import android.util.Size +import android.util.SparseIntArray +import android.view.Surface +import android.view.TextureView import android.view.ViewGroup -import com.simplemobiletools.camera.helpers.STATE_PREVIEW +import com.simplemobiletools.camera.activities.MainActivity +import com.simplemobiletools.camera.helpers.* import com.simplemobiletools.camera.interfaces.MyPreview +import java.util.* +import java.util.concurrent.Semaphore +import java.util.concurrent.TimeUnit -class PreviewCameraTwo(context: Context) : ViewGroup(context), MyPreview { - private var mTargetUri: Uri? = null +// based on the Android Camera2 sample at https://github.com/googlesamples/android-Camera2Basic +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +class PreviewCameraTwo : ViewGroup, TextureView.SurfaceTextureListener, MyPreview { + private val MAX_PREVIEW_WIDTH = 1920 + private val MAX_PREVIEW_HEIGHT = 1080 + + private val ORIENTATIONS = SparseIntArray().apply { + append(Surface.ROTATION_0, 90) + append(Surface.ROTATION_90, 0) + append(Surface.ROTATION_180, 270) + append(Surface.ROTATION_270, 180) + } + + private lateinit var mActivity: MainActivity + private lateinit var mTextureView: AutoFitTextureView + + private var mSensorOrientation = 0 + private var mIsFlashSupported = true private var mIsImageCaptureIntent = false + private var mCameraId = "" + private var mCameraState = STATE_PREVIEW + + private var mBackgroundThread: HandlerThread? = null + private var mBackgroundHandler: Handler? = null + private var mImageReader: ImageReader? = null + private var mPreviewSize: Size? = null + private var mTargetUri: Uri? = null + private var mCameraDevice: CameraDevice? = null + private var mCaptureSession: CameraCaptureSession? = null + private var mPreviewRequestBuilder: CaptureRequest.Builder? = null + private var mPreviewRequest: CaptureRequest? = null + private val mCameraOpenCloseLock = Semaphore(1) + + constructor(context: Context) : super(context) + + constructor(activity: MainActivity, textureView: AutoFitTextureView) : super(activity) { + mActivity = activity + mTextureView = textureView + } + + override fun onResumed() { + startBackgroundThread() + + if (mTextureView.isAvailable) { + openCamera(mTextureView.width, mTextureView.height) + } else { + mTextureView.surfaceTextureListener = this + } + } + + override fun onPaused() { + stopBackgroundThread() + } + + override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) { + configureTransform(width, height) + } + + override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {} + + override fun onSurfaceTextureDestroyed(surface: SurfaceTexture) = true + + override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { + openCamera(width, height) + } + + private fun startBackgroundThread() { + mBackgroundThread = HandlerThread("SimpleCameraBackground") + mBackgroundThread!!.start() + mBackgroundHandler = Handler(mBackgroundThread!!.looper) + } + + private fun stopBackgroundThread() { + mBackgroundThread?.quitSafely() + try { + mBackgroundThread?.join() + mBackgroundThread = null + mBackgroundHandler = null + } catch (e: InterruptedException) { + } + } + + private fun openCamera(width: Int, height: Int) { + setUpCameraOutputs(width, height) + configureTransform(width, height) + val manager = mActivity.getSystemService(Context.CAMERA_SERVICE) as CameraManager + try { + if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { + throw RuntimeException("Time out waiting to lock camera opening.") + } + manager.openCamera(mCameraId, cameraStateCallback, mBackgroundHandler) + } catch (e: Exception) { + } + } + + private val imageAvailableListener = ImageReader.OnImageAvailableListener { reader -> val image = reader.acquireNextImage() } + + private fun setUpCameraOutputs(width: Int, height: Int) { + val manager = mActivity.getSystemService(Context.CAMERA_SERVICE) as CameraManager + try { + for (cameraId in manager.cameraIdList) { + val characteristics = manager.getCameraCharacteristics(cameraId) + + val map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue + val largest = map.getOutputSizes(ImageFormat.JPEG).maxBy { it.width * it.height } + + mImageReader = ImageReader.newInstance(largest!!.width, largest.height, ImageFormat.JPEG, 2) + mImageReader!!.setOnImageAvailableListener(imageAvailableListener, mBackgroundHandler) + + val displayRotation = mActivity.windowManager.defaultDisplay.rotation + + mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!! + var swappedDimensions = false + when (displayRotation) { + Surface.ROTATION_0, Surface.ROTATION_180 -> if (mSensorOrientation == 90 || mSensorOrientation == 270) { + swappedDimensions = true + } + Surface.ROTATION_90, Surface.ROTATION_270 -> if (mSensorOrientation == 0 || mSensorOrientation == 180) { + swappedDimensions = true + } + } + + val displaySize = Point() + mActivity.windowManager.defaultDisplay.getSize(displaySize) + var rotatedPreviewWidth = width + var rotatedPreviewHeight = height + var maxPreviewWidth = displaySize.x + var 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 + } + + mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture::class.java), + rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth, + maxPreviewHeight, largest) + + val orientation = resources.configuration.orientation + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + mTextureView.setAspectRatio(mPreviewSize!!.width, mPreviewSize!!.height) + } else { + mTextureView.setAspectRatio(mPreviewSize!!.height, mPreviewSize!!.width) + } + + val available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) + mIsFlashSupported = available ?: false + mCameraId = cameraId + return + } + } catch (e: Exception) { + } + } + + private fun chooseOptimalSize(choices: Array, textureViewWidth: Int, textureViewHeight: Int, maxWidth: Int, maxHeight: Int, aspectRatio: Size): Size { + val bigEnough = ArrayList() + val notBigEnough = ArrayList() + val width = aspectRatio.width + val height = aspectRatio.height + for (option in choices) { + if (option.width <= maxWidth && option.height <= maxHeight && option.height == option.width * height / width) { + if (option.width >= textureViewWidth && option.height >= textureViewHeight) { + bigEnough.add(option) + } else { + notBigEnough.add(option) + } + } + } + + return when { + bigEnough.size > 0 -> bigEnough.minBy { it.width * it.height }!! + notBigEnough.size > 0 -> notBigEnough.maxBy { it.width * it.height }!! + else -> choices[0] + } + } + + private fun configureTransform(viewWidth: Int, viewHeight: Int) { + if (mPreviewSize == null) { + return + } + + val rotation = mActivity.windowManager.defaultDisplay.rotation + val matrix = Matrix() + val viewRect = RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat()) + val bufferRect = RectF(0f, 0f, mPreviewSize!!.height.toFloat(), mPreviewSize!!.width.toFloat()) + val centerX = viewRect.centerX() + val 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) + val scale = Math.max(viewHeight.toFloat() / mPreviewSize!!.height, viewWidth.toFloat() / mPreviewSize!!.width) + matrix.postScale(scale, scale, centerX, centerY) + matrix.postRotate((90 * (rotation - 2)).toFloat(), centerX, centerY) + } else if (Surface.ROTATION_180 == rotation) { + matrix.postRotate(180f, centerX, centerY) + } + + mTextureView.setTransform(matrix) + } + + private var cameraStateCallback = object : CameraDevice.StateCallback() { + override fun onOpened(cameraDevice: CameraDevice) { + mCameraOpenCloseLock.release() + mCameraDevice = cameraDevice + createCameraPreviewSession() + } + + override fun onDisconnected(cameraDevice: CameraDevice) { + mCameraOpenCloseLock.release() + cameraDevice.close() + mCameraDevice = null + } + + override fun onError(cameraDevice: CameraDevice, error: Int) { + mCameraOpenCloseLock.release() + cameraDevice.close() + mCameraDevice = null + } + } + + private fun createCameraPreviewSession() { + try { + val texture = mTextureView.surfaceTexture!! + texture.setDefaultBufferSize(mPreviewSize!!.width, mPreviewSize!!.height) + + val surface = Surface(texture) + mPreviewRequestBuilder = mCameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) + mPreviewRequestBuilder!!.addTarget(surface) + + mCameraDevice!!.createCaptureSession(Arrays.asList(surface, mImageReader!!.surface), + object : CameraCaptureSession.StateCallback() { + override fun onConfigured(cameraCaptureSession: CameraCaptureSession) { + if (mCameraDevice == null) { + return + } + + mCaptureSession = cameraCaptureSession + try { + mPreviewRequestBuilder!!.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) + setAutoFlash(mPreviewRequestBuilder!!) + mPreviewRequest = mPreviewRequestBuilder!!.build() + mCaptureSession!!.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler) + } catch (e: CameraAccessException) { + } + } + + override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) { + } + }, null + ) + } catch (e: CameraAccessException) { + } + } + + private fun setAutoFlash(requestBuilder: CaptureRequest.Builder) { + if (mIsFlashSupported) { + requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH) + } + } + + private val mCaptureCallback = object : CameraCaptureSession.CaptureCallback() { + private fun process(result: CaptureResult) { + when (mCameraState) { + STATE_WAITING_LOCK -> { + val afState = result.get(CaptureResult.CONTROL_AF_STATE) + if (afState == null) { + captureStillPicture() + } else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { + val aeState = result.get(CaptureResult.CONTROL_AE_STATE) + if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { + mCameraState = STATE_PICTURE_TAKEN + captureStillPicture() + } else { + runPrecaptureSequence() + } + } + } + STATE_WAITING_PRECAPTURE -> { + val aeState = result.get(CaptureResult.CONTROL_AE_STATE) + if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { + mCameraState = STATE_WAITING_NON_PRECAPTURE + } + } + STATE_WAITING_NON_PRECAPTURE -> { + val aeState = result.get(CaptureResult.CONTROL_AE_STATE) + if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { + mCameraState = STATE_PICTURE_TAKEN + captureStillPicture() + } + } + } + } + + override fun onCaptureProgressed(session: CameraCaptureSession, request: CaptureRequest, partialResult: CaptureResult) { + process(partialResult) + } + + override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) { + process(result) + } + } + + private fun runPrecaptureSequence() { + try { + mPreviewRequestBuilder!!.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START) + mCameraState = STATE_WAITING_PRECAPTURE + mCaptureSession!!.capture(mPreviewRequestBuilder!!.build(), mCaptureCallback, mBackgroundHandler) + } catch (e: CameraAccessException) { + } + } + + private fun captureStillPicture() { + try { + if (mCameraDevice == null) { + return + } + + val captureBuilder = mCameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE) + captureBuilder.addTarget(mImageReader!!.surface) + + captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) + setAutoFlash(captureBuilder) + + val rotation = mActivity.windowManager.defaultDisplay.rotation + captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)) + + val CaptureCallback = object : CameraCaptureSession.CaptureCallback() { + override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) { + unlockFocus() + } + } + + mCaptureSession!!.apply { + stopRepeating() + abortCaptures() + capture(captureBuilder.build(), CaptureCallback, null) + } + } catch (e: CameraAccessException) { + } + } + + private fun getOrientation(rotation: Int) = (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360 + + private fun unlockFocus() { + try { + mPreviewRequestBuilder!!.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL) + setAutoFlash(mPreviewRequestBuilder!!) + mCaptureSession!!.capture(mPreviewRequestBuilder!!.build(), mCaptureCallback, mBackgroundHandler) + mCameraState = STATE_PREVIEW + mCaptureSession!!.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler) + } catch (e: CameraAccessException) { + } + } + + private fun lockFocus() { + try { + mPreviewRequestBuilder!!.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START) + mCameraState = STATE_WAITING_LOCK + mCaptureSession!!.capture(mPreviewRequestBuilder!!.build(), mCaptureCallback, mBackgroundHandler) + } catch (e: CameraAccessException) { + } + } + + private fun takePicture() { + lockFocus() + } override fun setTargetUri(uri: Uri) { mTargetUri = uri @@ -42,6 +432,7 @@ class PreviewCameraTwo(context: Context) : ViewGroup(context), MyPreview { } override fun tryTakePicture() { + takePicture() } override fun toggleRecording(): Boolean {