EPForumL/androidApp

View on GitHub
app/src/main/java/com/github/ybecker/epforuml/features/camera/CameraActivity.kt

Summary

Maintainability
C
1 day
Test Coverage
F
16%
package com.github.ybecker.epforuml.features.camera

import android.Manifest
import android.content.ContentValues
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.PorterDuff
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.ImageCapture
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import android.widget.Toast
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.core.Preview
import androidx.camera.core.CameraSelector
import android.util.Log
import android.widget.ImageButton
import android.widget.Toast.LENGTH_SHORT
import androidx.camera.core.ImageCaptureException
import androidx.camera.video.*
import androidx.camera.view.PreviewView
import androidx.core.content.PermissionChecker
import com.github.ybecker.epforuml.R
import com.github.ybecker.epforuml.databinding.ActivityCameraBinding
import com.github.ybecker.epforuml.util.MainActivity
import java.text.SimpleDateFormat
import java.util.*


class CameraActivity : AppCompatActivity() {

    private lateinit var viewBinding: ActivityCameraBinding

    private var imageCapture: ImageCapture? = null
    private var videoCapture: VideoCapture<Recorder>? = null

    private var recording: Recording? = null

    private lateinit var cameraExecutor: ExecutorService

    private var isRecording = false

    private lateinit var viewFinder: PreviewView


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityCameraBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        // Request camera permissions
        if (allPermissionsGranted()) {
            startCamera()
        } else {
            ActivityCompat.requestPermissions(
                this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
            )
        }

        // Set up the listeners for take photo and video capture buttons
        val captureButton = viewBinding.imageCaptureButton


        captureButton.setOnClickListener {
            (it as ImageButton).setColorFilter(ContextCompat.getColor( MainActivity.context, R.color.shade3), PorterDuff.Mode.SRC_IN)
            viewBinding.imageCaptureButton.removeCallbacks(null)
            if (isRecording) {
                stopRecordingVideo()
            } else {
                takePhoto()
            }
        }

        captureButton.setOnLongClickListener {
            (it as ImageButton).setColorFilter(ContextCompat.getColor(MainActivity.context, R.color.shade2), PorterDuff.Mode.SRC_IN)
            startRecordingVideo()
            true
        }

        cameraExecutor = Executors.newSingleThreadExecutor()

        viewFinder = findViewById(R.id.viewFinder)
    }

    override fun onRequestPermissionsResult(
        requestCode: Int, permissions: Array<String>, grantResults:
        IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (allPermissionsGranted()) {
                startCamera()
            } else {
                Toast.makeText(this,
                    "Permissions not granted by the user.",
                    LENGTH_SHORT).show()
                finish()
            }
        }
    }

    private fun takePhoto() {

        // Get a stable reference of the modifiable image capture use case
        val imageCapture = imageCapture ?: return

        // Create time stamped name and MediaStore entry.
        val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
            .format(System.currentTimeMillis())
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, name)
            put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
            if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
                put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
            }
        }

        // Create output options object which contains file + metadata
        val outputOptions = ImageCapture.OutputFileOptions
            .Builder(contentResolver,
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                contentValues)
            .build()

        // Set up image capture listener, which is triggered after photo has
        // been taken
        imageCapture.takePicture(
            outputOptions,
            ContextCompat.getMainExecutor(this),
            object : ImageCapture.OnImageSavedCallback {
                override fun onError(exc: ImageCaptureException) {
                    Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
                }

                override fun
                        onImageSaved(output: ImageCapture.OutputFileResults){
                    val msg = "Photo capture succeeded: ${output.savedUri}"
                    Toast.makeText(baseContext, msg, LENGTH_SHORT).show()
                    Log.d(TAG, msg)
                    goToEdit(output.savedUri.toString())
                }

            }
        )

    }

    private fun startRecordingVideo() {

        val videoCapture = this.videoCapture ?: return

        isRecording=true

        val curRecording = recording
        if (curRecording != null) {
            curRecording.stop()
            recording = null
            return
        }

        val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
            .format(System.currentTimeMillis())
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, name)
            put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
                put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video")
            }
        }

        val context = this
        val mediaStoreOutputOptions = MediaStoreOutputOptions
            .Builder(contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
            .setContentValues(contentValues)
            .build()

        // launch a new Recording
        recording = videoCapture.output
            .prepareRecording(this, mediaStoreOutputOptions)
            .apply {
                if (PermissionChecker.checkSelfPermission(
                        context,
                        Manifest.permission.RECORD_AUDIO
                    ) ==
                    PermissionChecker.PERMISSION_GRANTED
                ) {
                    withAudioEnabled()
                }
            // start the new recording
            }.start(ContextCompat.getMainExecutor(context)) { recordEvent ->
            when (recordEvent) {
                is VideoRecordEvent.Start -> {
                    isRecording=true
                    Log.d(TAG, "Start recording")

                }
                //when the video ends
                is VideoRecordEvent.Finalize -> {
                    isRecording=false
                    val uri = recordEvent.outputResults.outputUri
                    //when no errors the video is saved correctly, we can go back the the question
                    if (!recordEvent.hasError()) {
                        val msg = "Video capture succeeded: " +
                                "$uri"
                        goBackToQuestion(uri.toString())
                        Log.d(TAG, msg)
                    //if there is an error stop recording, and stay on the activity so the user can retake a video
                    } else {
                        recording?.close()
                        recording = null
                        Toast.makeText(this, "Error during the recording", LENGTH_SHORT).show()
                        Log.e(
                            TAG, "Video capture ends with error: " +
                                "${recordEvent.error}")
                    }
                }
            }
        }
    }

    private fun stopRecordingVideo() {
        recording?.close()
    }



    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

        cameraProviderFuture.addListener({
            // Used to bind the lifecycle of cameras to the lifecycle owner
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            // Preview
            val preview = Preview.Builder()
                .build()
                .also {
                    it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider)
                }


            imageCapture = ImageCapture.Builder()
                .build()

            //Set up a new recorder in videoCapture with low quality (the free Firebase bandwidth is limited)
            val recorder = Recorder.Builder()
                .setExecutor(ContextCompat.getMainExecutor(this))
                .setQualitySelector(QualitySelector.from(Quality.LOWEST))
                .build()
            videoCapture = VideoCapture.withOutput(recorder)

            // Select back camera as a default
            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
                cameraProvider.bindToLifecycle(this, cameraSelector, preview, videoCapture)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }

    private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(
            baseContext, it
        ) == PackageManager.PERMISSION_GRANTED
    }

    private fun goToEdit(uri : String){
        val intent  = Intent(this, EditPhotoActivity::class.java)
        intent.putExtra("uri", uri)
        intent.putExtra("questionTitle", getIntent().getStringExtra("questionTitle"))
        intent.putExtra("questionDetails", getIntent().getStringExtra("questionDetails"))
        startActivity(intent)

    }

    private fun goBackToQuestion(uri : String){
        val intent  = Intent(this, MainActivity::class.java)
        intent.putExtra("uri", uri)
        intent.putExtra("fragment", "NewQuestionFragment")
        intent.putExtra("questionTitle", getIntent().getStringExtra("questionTitle"))
        intent.putExtra("questionDetails", getIntent().getStringExtra("questionDetails"))
        startActivity(intent)

    }

    override fun onDestroy() {
        super.onDestroy()
        cameraExecutor.shutdown()
    }

    companion object {
        private const val TAG = "CameraXApp"
        private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
        private const val REQUEST_CODE_PERMISSIONS = 10
        private val REQUIRED_PERMISSIONS =
            mutableListOf(
                Manifest.permission.CAMERA,
                Manifest.permission.RECORD_AUDIO
            ).apply {
                if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
                    add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                }
            }.toTypedArray()
    }
}