@ -10,14 +10,10 @@ 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
@ -29,14 +25,18 @@ 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.video_recording.SeekableWriter
import sushi.hardcore.droidfs.video_recording.VideoCapture
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import java.io.*
import sushi.hardcore.droidfs.widgets.EditTextDialog
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.Executor
@ -79,6 +79,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
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
@ -91,6 +92,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
usf _keep _open = sharedPrefs . getBoolean ( " usf_keep_open " , false )
binding = ActivityCameraBinding . inflate ( layoutInflater )
setContentView ( binding . root )
supportActionBar ?. hide ( )
gocryptfsVolume = GocryptfsVolume ( applicationContext , intent . getIntExtra ( " sessionID " , - 1 ) )
outputDirectory = intent . getStringExtra ( " path " ) !!
@ -127,7 +129,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
}
CustomAlertDialogBuilder ( 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
@ -139,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 ( )
}
@ -150,39 +156,27 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
resolutions ?. let {
CustomAlertDialogBuilder ( 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 = CustomAlertDialogBuilder ( this , themeValue )
. 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 . imageFlash . setOnClickListener {
binding . imageFlash . setImageResource ( if ( isInVideoMode ) {
@ -217,6 +211,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
}
binding . imageModeSwitch . setOnClickListener {
isInVideoMode = !is InVideoMode
setupCamera ( )
binding . imageFlash . setImageResource ( if ( isInVideoMode ) {
binding . recordVideoButton . visibility = View . VISIBLE
binding . takePhotoButton . visibility = View . GONE
@ -226,11 +221,15 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
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
} )
@ -248,6 +247,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
}
true
}
resolutions = null
setupCamera ( )
}
binding . takePhotoButton . onClick = :: onClickTakePhoto
@ -310,52 +310,67 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
private fun adaptPreviewSize ( resolution : Size ) {
val screenWidth = resources . displayMetrics . widthPixels
binding . cameraPreview . layoutParams = if ( screenWidth < resolution . width ) {
RelativeLayout . LayoutParams (
screenWidth ,
( resolution . height * ( screenWidth . toFloat ( ) / resolution . width ) ) . toInt ( )
)
} else {
RelativeLayout . LayoutParams ( resolution . width , resolution . height )
val screenHeight = resources . displayMetrics . heightPixels
var height = ( resolution . height * ( screenWidth . toFloat ( ) / resolution . width ) ) . toInt ( )
var width = screenWidth
if ( height > screenHeight ) {
width = ( width * ( screenHeight . toFloat ( ) / height ) ) . toInt ( )
height = screenHeight
}
binding . cameraPreview . layoutParams = RelativeLayout . LayoutParams ( width , height ) . apply {
addRule ( RelativeLayout . CENTER _IN _PARENT )
}
( binding . cameraPreview . layoutParams as RelativeLayout . LayoutParams ) . addRule ( RelativeLayout . CENTER _IN _PARENT )
}
@SuppressLint ( " RestrictedApi " )
private fun setupCamera ( resolution : Size ? = null ) {
if ( permissionsGranted && :: extensionsManager . isInitialized && :: cameraProvider . isInitialized ) {
imageCapture = ImageCapture . Builder ( )
. setCaptureMode ( captureMode )
. setFlashMode ( imageCapture ?. flashMode ?: ImageCapture . FLASH _MODE _AUTO )
. apply {
resolution ?. let {
setTargetResolution ( it )
}
}
. build ( )
videoCapture = VideoCapture . Builder ( ) . apply {
resolution ?. let {
private fun refreshImageCapture ( ) {
imageCapture = ImageCapture . Builder ( )
. setCaptureMode ( captureMode )
. setFlashMode ( imageCapture ?. flashMode ?: ImageCapture . FLASH _MODE _AUTO )
. apply {
currentResolution ?. let {
setTargetResolution ( it )
}
} . build ( )
}
. build ( )
}
private fun refreshVideoCapture ( ) {
videoCapture = VideoCapture . Builder ( ) . apply {
currentResolution ?. let {
setTargetResolution ( it )
}
} . build ( )
}
@SuppressLint ( " RestrictedApi " )
private fun setupCamera ( ) {
if ( permissionsGranted && :: extensionsManager . isInitialized && :: cameraProvider . isInitialized ) {
cameraSelector = if ( isBackCamera ) { CameraSelector . DEFAULT _BACK _CAMERA } else { CameraSelector . DEFAULT _FRONT _CAMERA }
if ( extensionsManager . isExtensionAvailable ( cameraSelector , ExtensionMode . HDR ) ) {
cameraSelector = extensionsManager . getExtensionEnabledCameraSelector ( cameraSelector , ExtensionMode . HDR )
if ( extensionsManager . isExtensionAvailable ( cameraSelector , ExtensionMode . AUTO ) ) {
cameraSelector = extensionsManager . getExtensionEnabledCameraSelector ( cameraSelector , ExtensionMode . AUTO )
}
cameraProvider . unbindAll ( )
camera = cameraProvider . bindToLifecycle ( this , cameraSelector , cameraPreview , imageCapture , videoCapture )
adaptPreviewSize ( resolution ?: imageCapture !! . attachedSurfaceResolution !! . swap ( ) )
val currentUseCase = ( if ( isInVideoMode ) {
refreshVideoCapture ( )
camera = cameraProvider . bindToLifecycle ( this , cameraSelector , cameraPreview , videoCapture )
videoCapture
} else {
refreshImageCapture ( )
camera = cameraProvider . bindToLifecycle ( this , cameraSelector , cameraPreview , imageCapture )
imageCapture
} ) !!
adaptPreviewSize ( currentResolution ?: currentUseCase . attachedSurfaceResolution !! . swap ( ) )
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 ( ) }
resolutions = streamConfigurationMap . getOutputSizes ( currentUseCase . imageFormat ) . map { it . swap ( ) }
}
}
}
@ -374,17 +389,17 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
if ( timerDuration > 0 ) {
binding . textTimer . visibility = View . VISIBLE
isWaitingForTimer = true
Thread {
lifecycleScope . launch {
for ( i in timerDuration downTo 1 ) {
runOnUiThread { binding . textTimer . text = i . toString ( ) }
Thread . sleep ( 1000 )
binding . textTimer . text = i . toString ( )
delay ( 1000 )
}
runOnUiThread {
if ( !is Finishing ) {
action ( )
binding . textTimer . visibility = View . GONE
}
isWaitingForTimer = false
} . start ( )
}
} else {
action ( )
}
@ -495,21 +510,24 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
}
override fun onOrientationChange ( newOrientation : Int ) {
val reversedOrientation = when ( newOrientation ) {
90 -> 270
270 -> 90
else -> newOrientation
} . toFloat ( )
val realOrientation = when ( newOrientation ) {
Surface . ROTATION _0 -> 0f
Surface . ROTATION _90 -> 90f
Surface . ROTATION _180 -> 180f
else -> 270f
}
val rotateAnimation = RotateAnimation ( previousOrientation , when {
reversed Orientation - previousOrientation > 180 -> reversed Orientation - 360
reversed Orientation - previousOrientation < - 180 -> reversed Orientation + 360
else -> reversed Orientation
real Orientation - previousOrientation > 180 -> real Orientation - 360
real Orientation - previousOrientation < - 180 -> real Orientation + 360
else -> real Orientation
} , Animation . RELATIVE _TO _SELF , 0.5f , Animation . RELATIVE _TO _SELF , 0.5f )
rotateAnimation . duration = 300
rotateAnimation . interpolator = LinearInterpolator ( )
rotateAnimation . fillAfter = true
orientedIcons . map { it . startAnimation ( rotateAnimation ) }
previousOrientation = reversedOrientation
previousOrientation = realOrientation
imageCapture ?. targetRotation = newOrientation
videoCapture ?. setTargetRotation ( newOrientation )
}
}