2022-07-13 17:08:20 +01:00

682 lines
22 KiB
Kotlin

package com.simplemobiletools.camera.activities
import android.app.Activity
import android.content.Intent
import android.graphics.Bitmap
import android.hardware.SensorManager
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.MediaStore
import android.view.*
import android.widget.RelativeLayout
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestOptions
import com.simplemobiletools.camera.BuildConfig
import com.simplemobiletools.camera.R
import com.simplemobiletools.camera.extensions.config
import com.simplemobiletools.camera.helpers.*
import com.simplemobiletools.camera.implementations.CameraXInitializer
import com.simplemobiletools.camera.implementations.CameraXPreviewListener
import com.simplemobiletools.camera.implementations.MyCameraImpl
import com.simplemobiletools.camera.interfaces.MyPreview
import com.simplemobiletools.camera.views.FocusCircleView
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.Release
import java.util.concurrent.TimeUnit
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, CameraXPreviewListener {
companion object {
private const val FADE_DELAY = 5000L
private const val CAPTURE_ANIMATION_DURATION = 100L
}
lateinit var mTimerHandler: Handler
private lateinit var mOrientationEventListener: OrientationEventListener
private lateinit var mFocusCircleView: FocusCircleView
private lateinit var mFadeHandler: Handler
private lateinit var mCameraImpl: MyCameraImpl
private var mPreview: MyPreview? = null
private var mPreviewUri: Uri? = null
private var mIsInPhotoMode = true
private var mIsCameraAvailable = false
private var mIsHardwareShutterHandled = false
private var mCurrVideoRecTimer = 0
var mLastHandledOrientation = 0
@Suppress("DEPRECATION")
override fun onCreate(savedInstanceState: Bundle?) {
useDynamicTheme = false
super.onCreate(savedInstanceState)
appLaunched(BuildConfig.APPLICATION_ID)
requestWindowFeature(Window.FEATURE_NO_TITLE)
initVariables()
tryInitCamera()
supportActionBar?.hide()
checkWhatsNewDialog()
setupOrientationEventListener()
if (isRPlus()) {
setShowWhenLocked(true)
setTurnScreenOn(true)
window.insetsController?.hide(WindowInsets.Type.statusBars())
} else if (isOreoMr1Plus()) {
setShowWhenLocked(true)
setTurnScreenOn(true)
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
} else {
window.addFlags(
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
}
}
override fun onResume() {
super.onResume()
if (hasStorageAndCameraPermissions()) {
resumeCameraItems()
setupPreviewImage(mIsInPhotoMode)
scheduleFadeOut()
mFocusCircleView.setStrokeColor(getProperPrimaryColor())
if (isVideoCaptureIntent() && mIsInPhotoMode) {
handleTogglePhotoVideo()
checkButtons()
}
toggleBottomButtons(false)
}
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
if (hasStorageAndCameraPermissions()) {
mOrientationEventListener.enable()
}
}
override fun onPause() {
super.onPause()
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
if (!hasStorageAndCameraPermissions() || isAskingPermissions) {
return
}
mFadeHandler.removeCallbacksAndMessages(null)
hideTimer()
mOrientationEventListener.disable()
}
override fun onDestroy() {
super.onDestroy()
mPreview = null
}
private fun initVariables() {
mIsInPhotoMode = if (isVideoCaptureIntent()) {
false
} else if (isImageCaptureIntent()) {
true
} else {
config.initPhotoMode
}
mIsCameraAvailable = false
mIsHardwareShutterHandled = false
mCurrVideoRecTimer = 0
mLastHandledOrientation = 0
mCameraImpl = MyCameraImpl(applicationContext)
config.lastUsedCamera = mCameraImpl.getBackCameraId().toString()
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
return if (keyCode == KeyEvent.KEYCODE_CAMERA && !mIsHardwareShutterHandled) {
mIsHardwareShutterHandled = true
shutterPressed()
true
} else if (!mIsHardwareShutterHandled && config.volumeButtonsAsShutter && (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP)) {
mIsHardwareShutterHandled = true
shutterPressed()
true
} else {
super.onKeyDown(keyCode, event)
}
}
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_CAMERA || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
mIsHardwareShutterHandled = false
}
return super.onKeyUp(keyCode, event)
}
private fun hideIntentButtons() {
toggle_photo_video.beGone()
settings.beGone()
last_photo_video_preview.beGone()
}
private fun tryInitCamera() {
handlePermission(PERMISSION_CAMERA) { grantedCameraPermission ->
if (grantedCameraPermission) {
handlePermission(PERMISSION_WRITE_STORAGE) { grantedStoragePermission ->
if (grantedStoragePermission) {
if (mIsInPhotoMode) {
initializeCamera()
} else {
handlePermission(PERMISSION_RECORD_AUDIO) { grantedRecordAudioPermission ->
if (grantedRecordAudioPermission) {
initializeCamera()
} else {
toast(R.string.no_audio_permissions)
togglePhotoVideoMode()
tryInitCamera()
}
}
}
} else {
toast(R.string.no_storage_permissions)
finish()
}
}
} else {
toast(R.string.no_camera_permissions)
finish()
}
}
}
private fun isImageCaptureIntent(): Boolean = intent?.action == MediaStore.ACTION_IMAGE_CAPTURE || intent?.action == MediaStore.ACTION_IMAGE_CAPTURE_SECURE
private fun isVideoCaptureIntent(): Boolean = intent?.action == MediaStore.ACTION_VIDEO_CAPTURE
private fun checkImageCaptureIntent() {
if (isImageCaptureIntent()) {
hideIntentButtons()
val output = intent.extras?.get(MediaStore.EXTRA_OUTPUT)
if (output != null && output is Uri) {
mPreview?.setTargetUri(output)
}
}
}
private fun checkVideoCaptureIntent() {
if (intent?.action == MediaStore.ACTION_VIDEO_CAPTURE) {
mIsInPhotoMode = false
hideIntentButtons()
shutter.setImageResource(R.drawable.ic_video_rec)
}
}
private fun initializeCamera() {
setContentView(R.layout.activity_main)
initButtons()
(btn_holder.layoutParams as RelativeLayout.LayoutParams).setMargins(
0,
0,
0,
(navigationBarHeight + resources.getDimension(R.dimen.activity_margin)).toInt()
)
checkVideoCaptureIntent()
val outputUri = intent.extras?.get(MediaStore.EXTRA_OUTPUT) as? Uri
val is3rdPartyIntent = isVideoCaptureIntent() || isImageCaptureIntent()
mPreview = CameraXInitializer(this).createCameraXPreview(
preview_view,
listener = this,
outputUri = outputUri,
is3rdPartyIntent = is3rdPartyIntent,
initInPhotoMode = mIsInPhotoMode,
)
checkImageCaptureIntent()
mPreview?.setIsImageCaptureIntent(isImageCaptureIntent())
val imageDrawable =
if (config.lastUsedCamera == mCameraImpl.getBackCameraId().toString()) R.drawable.ic_camera_front_vector else R.drawable.ic_camera_rear_vector
toggle_camera.setImageResource(imageDrawable)
mFocusCircleView = FocusCircleView(applicationContext)
view_holder.addView(mFocusCircleView)
mTimerHandler = Handler(Looper.getMainLooper())
mFadeHandler = Handler(Looper.getMainLooper())
setupPreviewImage(true)
val initialFlashlightState = FLASH_OFF
mPreview!!.setFlashlightState(initialFlashlightState)
updateFlashlightState(initialFlashlightState)
}
private fun initButtons() {
toggle_camera.setOnClickListener { toggleCamera() }
last_photo_video_preview.setOnClickListener { showLastMediaPreview() }
toggle_flash.setOnClickListener { toggleFlash() }
shutter.setOnClickListener { shutterPressed() }
settings.setOnClickListener { launchSettings() }
toggle_photo_video.setOnClickListener { handleTogglePhotoVideo() }
change_resolution.setOnClickListener { mPreview?.showChangeResolutionDialog() }
}
private fun toggleCamera() {
if (checkCameraAvailable()) {
mPreview!!.toggleFrontBackCamera()
}
}
private fun showLastMediaPreview() {
if (mPreviewUri != null) {
val path = applicationContext.getRealPathFromURI(mPreviewUri!!) ?: mPreviewUri!!.toString()
openPathIntent(path, false, BuildConfig.APPLICATION_ID)
}
}
private fun toggleFlash() {
if (checkCameraAvailable()) {
mPreview?.toggleFlashlight()
}
}
fun updateFlashlightState(state: Int) {
config.flashlightState = state
val flashDrawable = when (state) {
FLASH_OFF -> R.drawable.ic_flash_off_vector
FLASH_ON -> R.drawable.ic_flash_on_vector
else -> R.drawable.ic_flash_auto_vector
}
toggle_flash.setImageResource(flashDrawable)
}
private fun shutterPressed() {
if (checkCameraAvailable()) {
handleShutter()
}
}
private fun handleShutter() {
if (mIsInPhotoMode) {
toggleBottomButtons(true)
mPreview?.tryTakePicture()
capture_black_screen.animate().alpha(0.8f).setDuration(CAPTURE_ANIMATION_DURATION).withEndAction {
capture_black_screen.animate().alpha(0f).setDuration(CAPTURE_ANIMATION_DURATION).start()
}.start()
} else {
mPreview?.toggleRecording()
}
}
private fun launchSettings() {
if (settings.alpha == 1f) {
val intent = Intent(applicationContext, SettingsActivity::class.java)
startActivity(intent)
} else {
fadeInButtons()
}
}
private fun handleTogglePhotoVideo() {
handlePermission(PERMISSION_RECORD_AUDIO) {
if (it) {
togglePhotoVideo()
} else {
toast(R.string.no_audio_permissions)
if (isVideoCaptureIntent()) {
finish()
}
}
}
}
private fun togglePhotoVideo() {
if (!checkCameraAvailable()) {
return
}
if (isVideoCaptureIntent()) {
mPreview?.initVideoMode()
}
mPreview?.setFlashlightState(FLASH_OFF)
hideTimer()
togglePhotoVideoMode()
checkButtons()
toggleBottomButtons(false)
}
private fun togglePhotoVideoMode() {
mIsInPhotoMode = !mIsInPhotoMode
config.initPhotoMode = mIsInPhotoMode
}
private fun checkButtons() {
if (mIsInPhotoMode) {
initPhotoMode()
} else {
tryInitVideoMode()
}
}
private fun initPhotoMode() {
toggle_photo_video.setImageResource(R.drawable.ic_video_vector)
shutter.setImageResource(R.drawable.ic_shutter_vector)
mPreview?.initPhotoMode()
setupPreviewImage(true)
}
private fun tryInitVideoMode() {
try {
mPreview?.initVideoMode()
initVideoButtons()
} catch (e: Exception) {
if (!isVideoCaptureIntent()) {
toast(R.string.video_mode_error)
}
}
}
private fun initVideoButtons() {
toggle_photo_video.setImageResource(R.drawable.ic_camera_vector)
shutter.setImageResource(R.drawable.ic_video_rec)
setupPreviewImage(false)
mPreview?.checkFlashlight()
}
private fun setupPreviewImage(isPhoto: Boolean) {
val uri = if (isPhoto) MediaStore.Images.Media.EXTERNAL_CONTENT_URI else MediaStore.Video.Media.EXTERNAL_CONTENT_URI
val lastMediaId = getLatestMediaId(uri)
if (lastMediaId == 0L) {
return
}
mPreviewUri = Uri.withAppendedPath(uri, lastMediaId.toString())
loadLastTakenMedia(mPreviewUri)
}
private fun loadLastTakenMedia(uri: Uri?) {
mPreviewUri = uri
runOnUiThread {
if (!isDestroyed) {
val options = RequestOptions()
.centerCrop()
.diskCacheStrategy(DiskCacheStrategy.NONE)
Glide.with(this)
.load(uri)
.apply(options)
.transition(DrawableTransitionOptions.withCrossFade())
.into(last_photo_video_preview)
}
}
}
private fun scheduleFadeOut() {
if (!config.keepSettingsVisible) {
mFadeHandler.postDelayed({
fadeOutButtons()
}, FADE_DELAY)
}
}
private fun fadeOutButtons() {
fadeAnim(settings, .5f)
fadeAnim(toggle_photo_video, .0f)
fadeAnim(change_resolution, .0f)
fadeAnim(last_photo_video_preview, .0f)
}
private fun fadeInButtons() {
fadeAnim(settings, 1f)
fadeAnim(toggle_photo_video, 1f)
fadeAnim(change_resolution, 1f)
fadeAnim(last_photo_video_preview, 1f)
scheduleFadeOut()
}
private fun fadeAnim(view: View, value: Float) {
view.animate().alpha(value).start()
view.isClickable = value != .0f
}
@Suppress("DEPRECATION")
private fun hideNavigationBarIcons() {
if (isRPlus()) {
window.insetsController?.hide(WindowInsets.Type.systemBars())
} else {
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE
}
}
private fun showTimer() {
video_rec_curr_timer.beVisible()
setupTimer()
}
private fun hideTimer() {
video_rec_curr_timer.text = 0.getFormattedDuration()
video_rec_curr_timer.beGone()
mCurrVideoRecTimer = 0
mTimerHandler.removeCallbacksAndMessages(null)
}
private fun setupTimer() {
runOnUiThread(object : Runnable {
override fun run() {
video_rec_curr_timer.text = mCurrVideoRecTimer++.getFormattedDuration()
mTimerHandler.postDelayed(this, 1000L)
}
})
}
private fun resumeCameraItems() {
hideNavigationBarIcons()
if (!mIsInPhotoMode) {
initVideoButtons()
}
}
private fun hasStorageAndCameraPermissions(): Boolean {
return if (mIsInPhotoMode) {
hasPermission(PERMISSION_WRITE_STORAGE) && hasPermission(PERMISSION_CAMERA)
} else {
hasPermission(PERMISSION_WRITE_STORAGE) && hasPermission(PERMISSION_CAMERA) && hasPermission(PERMISSION_RECORD_AUDIO)
}
}
private fun setupOrientationEventListener() {
mOrientationEventListener = object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) {
override fun onOrientationChanged(orientation: Int) {
if (isDestroyed) {
mOrientationEventListener.disable()
return
}
val currOrient = when (orientation) {
in 75..134 -> ORIENT_LANDSCAPE_RIGHT
in 225..289 -> ORIENT_LANDSCAPE_LEFT
else -> ORIENT_PORTRAIT
}
if (currOrient != mLastHandledOrientation) {
val degrees = when (currOrient) {
ORIENT_LANDSCAPE_LEFT -> 90
ORIENT_LANDSCAPE_RIGHT -> -90
else -> 0
}
animateViews(degrees)
mLastHandledOrientation = currOrient
}
}
}
}
private fun animateViews(degrees: Int) {
val views = arrayOf<View>(toggle_camera, toggle_flash, toggle_photo_video, change_resolution, shutter, settings, last_photo_video_preview)
for (view in views) {
rotate(view, degrees)
}
}
private fun rotate(view: View, degrees: Int) = view.animate().rotation(degrees.toFloat()).start()
private fun checkCameraAvailable(): Boolean {
if (!mIsCameraAvailable) {
toast(R.string.camera_unavailable)
}
return mIsCameraAvailable
}
override fun setCameraAvailable(available: Boolean) {
mIsCameraAvailable = available
}
override fun setHasFrontAndBackCamera(hasFrontAndBack: Boolean) {
toggle_camera?.beVisibleIf(hasFrontAndBack)
}
override fun setFlashAvailable(available: Boolean) {
if (available) {
toggle_flash.beVisible()
} else {
toggle_flash.beInvisible()
toggle_flash.setImageResource(R.drawable.ic_flash_off_vector)
mPreview?.setFlashlightState(FLASH_OFF)
}
}
override fun onChangeCamera(frontCamera: Boolean) {
toggle_camera.setImageResource(if (frontCamera) R.drawable.ic_camera_rear_vector else R.drawable.ic_camera_front_vector)
}
override fun toggleBottomButtons(hide: Boolean) {
runOnUiThread {
val alpha = if (hide) 0f else 1f
shutter.animate().alpha(alpha).start()
toggle_camera.animate().alpha(alpha).start()
toggle_flash.animate().alpha(alpha).start()
shutter.isClickable = !hide
toggle_camera.isClickable = !hide
toggle_flash.isClickable = !hide
}
}
override fun onMediaSaved(uri: Uri) {
loadLastTakenMedia(uri)
if (isImageCaptureIntent()) {
Intent().apply {
data = uri
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
setResult(Activity.RESULT_OK, this)
}
finish()
} else if (isVideoCaptureIntent()) {
Intent().apply {
data = uri
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
setResult(Activity.RESULT_OK, this)
}
finish()
}
}
override fun onImageCaptured(bitmap: Bitmap) {
if (isImageCaptureIntent()) {
Intent().apply {
putExtra("data", bitmap)
setResult(Activity.RESULT_OK, this)
}
finish()
}
}
override fun onChangeFlashMode(flashMode: Int) {
updateFlashlightState(flashMode)
}
override fun onVideoRecordingStarted() {
shutter.setImageResource(R.drawable.ic_video_stop)
toggle_camera.beInvisible()
video_rec_curr_timer.beVisible()
}
override fun onVideoRecordingStopped() {
shutter.setImageResource(R.drawable.ic_video_rec)
video_rec_curr_timer.text = 0.getFormattedDuration()
video_rec_curr_timer.beGone()
toggle_camera.beVisible()
}
override fun onVideoDurationChanged(durationNanos: Long) {
val seconds = TimeUnit.NANOSECONDS.toSeconds(durationNanos).toInt()
video_rec_curr_timer.text = seconds.getFormattedDuration()
}
override fun onFocusCamera(xPos: Float, yPos: Float) {
mFocusCircleView.drawFocusCircle(xPos, yPos)
}
fun setRecordingState(isRecording: Boolean) {
runOnUiThread {
if (isRecording) {
shutter.setImageResource(R.drawable.ic_video_stop)
toggle_camera.beInvisible()
showTimer()
} else {
shutter.setImageResource(R.drawable.ic_video_rec)
hideTimer()
}
}
}
fun videoSaved(uri: Uri) {
setupPreviewImage(false)
if (isVideoCaptureIntent()) {
Intent().apply {
data = uri
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
setResult(Activity.RESULT_OK, this)
}
finish()
}
}
fun drawFocusCircle(x: Float, y: Float) = mFocusCircleView.drawFocusCircle(x, y)
override fun mediaSaved(path: String) {
rescanPaths(arrayListOf(path)) {
setupPreviewImage(true)
Intent(BROADCAST_REFRESH_MEDIA).apply {
putExtra(REFRESH_PATH, path)
`package` = "com.simplemobiletools.gallery"
sendBroadcast(this)
}
}
if (isImageCaptureIntent()) {
setResult(Activity.RESULT_OK)
finish()
}
}
private fun checkWhatsNewDialog() {
arrayListOf<Release>().apply {
add(Release(33, R.string.release_33))
add(Release(35, R.string.release_35))
add(Release(39, R.string.release_39))
add(Release(44, R.string.release_44))
add(Release(46, R.string.release_46))
add(Release(52, R.string.release_52))
checkWhatsNew(this, BuildConfig.VERSION_CODE)
}
}
}