@ -1,6 +1,7 @@
package sushi.hardcore.droidfs
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.hardware.camera2.CameraCharacteristics
@ -9,28 +10,31 @@ import android.os.Build
import android.os.Bundle
import android.text.InputType
import android.util.Size
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.View
import android.view.WindowManager
import android.view.*
import android.view.animation.Animation
import android.view.animation.LinearInterpolator
import android.view.animation.RotateAnimation
import android.widget.EditText
import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.core.*
import androidx.camera.extensions.ExtensionMode
import androidx.camera.extensions.ExtensionsManager
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import sushi.hardcore.droidfs.adapters.DialogSingleChoiceAdapter
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import sushi.hardcore.droidfs.content_providers.RestrictedFileProvider
import sushi.hardcore.droidfs.databinding.ActivityCameraBinding
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
import sushi.hardcore.droidfs.video_recording.SeekableWriter
import sushi.hardcore.droidfs.video_recording.VideoCapture
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import sushi.hardcore.droidfs.widgets.EditTextDialog
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat
@ -39,7 +43,8 @@ import java.util.concurrent.Executor
class CameraActivity : BaseActivity ( ) , SensorOrientationListener . Listener {
companion object {
private const val CAMERA _PERMISSION _REQUEST _CODE = 1
private const val CAMERA _PERMISSION _REQUEST _CODE = 0
private const val AUDIO _PERMISSION _REQUEST _CODE = 1
private const val fileNameRandomMin = 100000
private const val fileNameRandomMax = 999999
private val dateFormat = SimpleDateFormat ( " yyyyMMdd_HHmmss " , Locale . US )
@ -61,19 +66,25 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
private lateinit var orientedIcons : List < ImageView >
private lateinit var gocryptfsVolume : GocryptfsVolume
private lateinit var outputDirectory : String
private lateinit var fileName : String
private var isFinishingIntentionally = false
private var isAskingPermissions = false
private var permissionsGranted = false
private lateinit var executor : Executor
private lateinit var cameraProvider : ProcessCameraProvider
private lateinit var extensionsManager : ExtensionsManager
private lateinit var cameraSelector : CameraSelector
private val cameraPreview = Preview . Builder ( ) . build ( )
private var imageCapture : ImageCapture ? = null
private var videoCapture : VideoCapture ? = null
private var camera : Camera ? = null
private var resolutions : List < Size > ? = null
private var currentResolutionIndex : Int = 0
private var currentResolution : Size ? = null
private var captureMode = ImageCapture . CAPTURE _MODE _MAXIMIZE _QUALITY
private var isBackCamera = true
private var isInVideoMode = false
private var isRecording = false
private var isWaitingForTimer = false
private lateinit var binding : ActivityCameraBinding
override fun onCreate ( savedInstanceState : Bundle ? ) {
@ -81,13 +92,15 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
usf _keep _open = sharedPrefs . getBoolean ( " usf_keep_open " , false )
binding = ActivityCameraBinding . inflate ( layoutInflater )
setContentView ( binding . root )
gocryptfsVolume = GocryptfsVolume ( intent . getIntExtra ( " sessionID " , - 1 ) )
supportActionBar ?. hide ( )
gocryptfsVolume = GocryptfsVolume ( applicationContext , intent . getIntExtra ( " sessionID " , - 1 ) )
outputDirectory = intent . getStringExtra ( " path " ) !!
if ( Build . VERSION . SDK _INT >= Build . VERSION _CODES . M ) {
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . CAMERA ) == PackageManager . PERMISSION _GRANTED ) {
permissionsGranted = true
} else {
isAskingPermissions = true
requestPermissions ( arrayOf ( Manifest . permission . CAMERA ) , CAMERA _PERMISSION _REQUEST _CODE )
}
} else {
@ -99,13 +112,12 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
ProcessCameraProvider . getInstance ( this ) . apply {
addListener ( {
cameraProvider = get ( )
setupCamera ( )
} , executor )
}
ExtensionsManager . getInstance ( this ) . apply {
addListener ( {
extensionsManager = get ( )
setupCamera ( )
ExtensionsManager . getInstanceAsync ( this @CameraActivity , cameraProvider ) . apply {
addListener ( {
extensionsManager = get ( )
setupCamera ( )
} , executor )
}
} , executor )
}
@ -115,9 +127,9 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
} else {
1
}
Colored AlertDialogBuilder ( this )
Custom AlertDialogBuilder ( this , themeValue )
. setTitle ( R . string . camera _optimization )
. setSingleChoiceItems ( DialogSingleChoiceAdapter ( this , arrayOf ( R . string . maximize _quality , R . string . minimize _latency ) . map { getString ( it ) } ) , currentIndex ) { dialog , which ->
. setSingleChoiceItems ( arrayOf ( getString ( R . string . maximize _quality ) , getString ( R . string . minimize _latency ) ) , currentIndex ) { dialog , which ->
val resId : Int
val newCaptureMode = if ( which == 0 ) {
resId = R . drawable . icon _high _quality
@ -129,7 +141,11 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
if ( newCaptureMode != captureMode ) {
captureMode = newCaptureMode
binding . imageCaptureMode . setImageResource ( resId )
setupCamera ( )
if ( !is InVideoMode ) {
cameraProvider . unbind ( imageCapture )
refreshImageCapture ( )
cameraProvider . bindToLifecycle ( this , cameraSelector , imageCapture )
}
}
dialog . dismiss ( )
}
@ -138,60 +154,84 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
}
binding . imageRatio . setOnClickListener {
resolutions ?. let {
Colored AlertDialogBuilder ( this )
Custom AlertDialogBuilder ( this , themeValue )
. setTitle ( R . string . choose _resolution )
. setSingleChoiceItems ( DialogSingleChoiceAdapter ( this , it . map { size -> size . toString ( ) } ) , currentResolutionIndex ) { dialog , which ->
setupCamera ( resolutions !! [ which ] )
dialog . dismiss ( )
. setSingleChoiceItems ( it . map { size -> size . toString ( ) } . toTypedArray ( ) , currentResolutionIndex ) { dialog , which ->
currentResolution = resolutions !! [ which ]
currentResolutionIndex = which
setupCamera ( )
dialog . dismiss ( )
}
. setNegativeButton ( R . string . cancel , null )
. show ( )
}
}
binding . imageTimer . setOnClickListener {
val dialogEditTextView = layoutInflater . inflate ( R . layout . dialog _edit _text , null )
val dialogEditText = dialogEditTextView . findViewById < EditText > ( R . id . dialog _edit _text )
dialogEditText . inputType = InputType . TYPE _CLASS _NUMBER
val dialog = ColoredAlertDialogBuilder ( this )
. setView ( dialogEditTextView )
. setTitle ( getString ( R . string . enter _timer _duration ) )
. setPositiveButton ( R . string . ok ) { _ , _ ->
val enteredValue = dialogEditText . text . toString ( )
if ( enteredValue . isEmpty ( ) ) {
Toast . makeText ( this , getString ( R . string . timer _empty _error _msg ) , Toast . LENGTH _SHORT ) . show ( )
} else {
timerDuration = enteredValue . toInt ( )
}
with ( EditTextDialog ( this , R . string . enter _timer _duration ) {
try {
timerDuration = it . toInt ( )
} catch ( e : NumberFormatException ) {
Toast . makeText ( this , R . string . invalid _number , Toast . LENGTH _SHORT ) . show ( )
}
. setNegativeButton ( R . string . cancel , null )
. create ( )
dialogEditText . setOnEditorActionListener { _ , _ , _ ->
timerDuration = dialogEditText . text . toString ( ) . toInt ( )
dialog . dismiss ( )
true
} ) {
binding . dialogEditText . inputType = InputType . TYPE _CLASS _NUMBER
show ( )
}
dialog . window ?. setSoftInputMode ( WindowManager . LayoutParams . SOFT _INPUT _STATE _ALWAYS _VISIBLE )
dialog . show ( )
}
binding . imageClose . setOnClickListener {
isFinishingIntentionally = true
finish ( )
}
binding . imageFlash . setOnClickListener {
binding . imageFlash . setImageResource ( when ( imageCapture ?. flashMode ) {
ImageCapture . FLASH _MODE _AUTO -> {
imageCapture ?. flashMode = ImageCapture . FLASH _MODE _ON
R . drawable . icon _flash _on
binding . imageFlash . setImageResource ( if ( isInVideoMode ) {
when ( imageCapture ?. flashMode ) {
ImageCapture . FLASH _MODE _ON -> {
camera ?. cameraControl ?. enableTorch ( false )
imageCapture ?. flashMode = ImageCapture . FLASH _MODE _OFF
R . drawable . icon _flash _off
}
else -> {
camera ?. cameraControl ?. enableTorch ( true )
imageCapture ?. flashMode = ImageCapture . FLASH _MODE _ON
R . drawable . icon _flash _on
}
}
ImageCapture . FLASH _MODE _ON -> {
imageCapture ?. flashMode = ImageCapture . FLASH _MODE _OFF
R . drawable . icon _flash _off
} else {
when ( imageCapture ?. flashMode ) {
ImageCapture . FLASH _MODE _AUTO -> {
imageCapture ?. flashMode = ImageCapture . FLASH _MODE _ON
R . drawable . icon _flash _on
}
ImageCapture . FLASH _MODE _ON -> {
imageCapture ?. flashMode = ImageCapture . FLASH _MODE _OFF
R . drawable . icon _flash _off
}
else -> {
imageCapture ?. flashMode = ImageCapture . FLASH _MODE _AUTO
R . drawable . icon _flash _auto
}
}
else -> {
imageCapture ?. flashMode = ImageCapture . FLASH _MODE _AUTO
R . drawable . icon _flash _auto
} )
}
binding . imageModeSwitch . setOnClickListener {
isInVideoMode = !is InVideoMode
setupCamera ( )
binding . imageFlash . setImageResource ( if ( isInVideoMode ) {
binding . recordVideoButton . visibility = View . VISIBLE
binding . takePhotoButton . visibility = View . GONE
if ( Build . VERSION . SDK _INT >= Build . VERSION _CODES . M ) {
if ( ActivityCompat . checkSelfPermission ( this , Manifest . permission . RECORD _AUDIO ) != PackageManager . PERMISSION _GRANTED ) {
isAskingPermissions = true
requestPermissions ( arrayOf ( Manifest . permission . RECORD _AUDIO ) , AUDIO _PERMISSION _REQUEST _CODE )
}
}
binding . imageModeSwitch . setImageDrawable ( ContextCompat . getDrawable ( this , R . drawable . icon _photo ) ?. mutate ( ) ?. also {
it . setTint ( ContextCompat . getColor ( this , R . color . neutralIconTint ) )
} )
imageCapture ?. flashMode = ImageCapture . FLASH _MODE _OFF
R . drawable . icon _flash _off
} else {
binding . recordVideoButton . visibility = View . GONE
binding . takePhotoButton . visibility = View . VISIBLE
binding . imageModeSwitch . setImageResource ( R . drawable . icon _video )
imageCapture ?. flashMode = ImageCapture . FLASH _MODE _AUTO
R . drawable . icon _flash _auto
} )
}
binding . imageCameraSwitch . setOnClickListener {
@ -200,12 +240,19 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
false
} else {
binding . imageCameraSwitch . setImageResource ( R . drawable . icon _camera _front )
if ( isInVideoMode ) {
//reset flash state
imageCapture ?. flashMode = ImageCapture . FLASH _MODE _OFF
binding . imageFlash . setImageResource ( R . drawable . icon _flash _off )
}
true
}
resolutions = null
setupCamera ( )
}
binding . takePhotoButton . onClick = :: onClickTakePhoto
orientedIcons = listOf ( binding . imageRatio , binding . imageTimer , binding . imageClose , binding . imageFlash , binding . imageCameraSwitch )
binding . recordVideoButton . setOnClickListener { onClickRecordVideo ( ) }
orientedIcons = listOf ( binding . imageRatio , binding . imageTimer , binding . imageCaptureMode , binding . imageFlash , binding . imageModeSwitch , binding . imageCameraSwitch )
sensorOrientationListener = SensorOrientationListener ( this )
val scaleGestureDetector = ScaleGestureDetector ( this , object : ScaleGestureDetector . SimpleOnScaleGestureListener ( ) {
@ -232,15 +279,17 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
}
}
@RequiresApi ( Build . VERSION _CODES . M )
override fun onRequestPermissionsResult ( requestCode : Int , permissions : Array < String > , grantResults : IntArray ) {
super . onRequestPermissionsResult ( requestCode , permissions , grantResults )
when ( requestCode ) {
CAMERA _PERMISSION _REQUEST _CODE -> if ( grantResults . size == 1 ) {
if ( grantResults [ 0 ] == PackageManager . PERMISSION _GRANTED ) {
isAskingPermissions = false
if ( grantResults . size == 1 ) {