mirror of
https://github.com/SimpleMobileTools/Simple-Camera.git
synced 2025-03-25 21:10:14 +01:00
595 lines
19 KiB
Kotlin
595 lines
19 KiB
Kotlin
package com.simplemobiletools.camera.activities
|
|
|
|
import android.app.Activity
|
|
import android.content.Intent
|
|
import android.hardware.Camera
|
|
import android.hardware.SensorManager
|
|
import android.net.Uri
|
|
import android.os.Bundle
|
|
import android.os.Handler
|
|
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.extensions.navBarHeight
|
|
import com.simplemobiletools.camera.helpers.*
|
|
import com.simplemobiletools.camera.views.FocusRectView
|
|
import com.simplemobiletools.camera.views.Preview
|
|
import com.simplemobiletools.camera.views.Preview.PreviewListener
|
|
import com.simplemobiletools.commons.extensions.*
|
|
import com.simplemobiletools.commons.helpers.BROADCAST_REFRESH_MEDIA
|
|
import com.simplemobiletools.commons.helpers.PERMISSION_CAMERA
|
|
import com.simplemobiletools.commons.helpers.PERMISSION_RECORD_AUDIO
|
|
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
|
|
import com.simplemobiletools.commons.models.Release
|
|
import kotlinx.android.synthetic.main.activity_main.*
|
|
|
|
class MainActivity : SimpleActivity(), PreviewListener, PhotoProcessor.MediaSavedListener {
|
|
private val FADE_DELAY = 5000
|
|
|
|
lateinit var mFocusRectView: FocusRectView
|
|
lateinit var mTimerHandler: Handler
|
|
lateinit var mFadeHandler: Handler
|
|
|
|
private var mPreview: Preview? = null
|
|
private var mPreviewUri: Uri? = null
|
|
private var mFlashlightState = FLASH_OFF
|
|
private var mIsInPhotoMode = false
|
|
private var mIsCameraAvailable = false
|
|
private var mIsVideoCaptureIntent = false
|
|
private var mIsHardwareShutterHandled = false
|
|
private var mCurrVideoRecTimer = 0
|
|
private var mCurrCameraId = 0
|
|
var mLastHandledOrientation = 0
|
|
|
|
lateinit var mOrientationEventListener: OrientationEventListener
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
|
|
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
|
|
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
|
|
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
|
|
|
|
useDynamicTheme = false
|
|
super.onCreate(savedInstanceState)
|
|
appLaunched()
|
|
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
|
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
|
if (config.alwaysOpenBackCamera)
|
|
config.lastUsedCamera = Camera.CameraInfo.CAMERA_FACING_BACK
|
|
|
|
initVariables()
|
|
tryInitCamera()
|
|
supportActionBar?.hide()
|
|
checkWhatsNewDialog()
|
|
setupOrientationEventListener()
|
|
}
|
|
|
|
override fun onResume() {
|
|
super.onResume()
|
|
if (hasStorageAndCameraPermissions()) {
|
|
resumeCameraItems()
|
|
setupPreviewImage(mIsInPhotoMode)
|
|
scheduleFadeOut()
|
|
mFocusRectView.setStrokeColor(config.primaryColor)
|
|
|
|
if (mIsVideoCaptureIntent && 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()
|
|
mPreview?.releaseCamera()
|
|
mOrientationEventListener.disable()
|
|
|
|
if (mPreview?.isWaitingForTakePictureCallback == true) {
|
|
toast(R.string.photo_not_saved)
|
|
}
|
|
}
|
|
|
|
override fun onDestroy() {
|
|
super.onDestroy()
|
|
mPreview?.releaseCamera()
|
|
mPreview?.mActivity = null
|
|
mPreview = null
|
|
}
|
|
|
|
private fun initVariables() {
|
|
mIsInPhotoMode = false
|
|
mIsCameraAvailable = false
|
|
mIsVideoCaptureIntent = false
|
|
mIsHardwareShutterHandled = false
|
|
mCurrVideoRecTimer = 0
|
|
mCurrCameraId = 0
|
|
mLastHandledOrientation = 0
|
|
}
|
|
|
|
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
|
return if (keyCode == KeyEvent.KEYCODE_CAMERA && !mIsHardwareShutterHandled) {
|
|
mIsHardwareShutterHandled = true
|
|
shutterPressed()
|
|
true
|
|
} else if (config.volumeButtonsAsShutter && (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP)) {
|
|
shutterPressed()
|
|
true
|
|
} else {
|
|
super.onKeyDown(keyCode, event)
|
|
}
|
|
}
|
|
|
|
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
|
|
if (keyCode == KeyEvent.KEYCODE_CAMERA) {
|
|
mIsHardwareShutterHandled = false
|
|
}
|
|
return super.onKeyUp(keyCode, event)
|
|
}
|
|
|
|
private fun hideToggleModeAbout() {
|
|
toggle_photo_video.beGone()
|
|
settings.beGone()
|
|
}
|
|
|
|
private fun tryInitCamera() {
|
|
handlePermission(PERMISSION_CAMERA) {
|
|
if (it) {
|
|
handlePermission(PERMISSION_WRITE_STORAGE) {
|
|
if (it) {
|
|
initializeCamera()
|
|
handleIntent()
|
|
} else {
|
|
toast(R.string.no_storage_permissions)
|
|
finish()
|
|
}
|
|
}
|
|
} else {
|
|
toast(R.string.no_camera_permissions)
|
|
finish()
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun handleIntent() {
|
|
if (isImageCaptureIntent()) {
|
|
hideToggleModeAbout()
|
|
val output = intent.extras?.get(MediaStore.EXTRA_OUTPUT)
|
|
if (output != null && output is Uri) {
|
|
mPreview?.mTargetUri = output
|
|
}
|
|
} else if (intent?.action == MediaStore.ACTION_VIDEO_CAPTURE) {
|
|
mIsVideoCaptureIntent = true
|
|
hideToggleModeAbout()
|
|
shutter.setImageDrawable(resources.getDrawable(R.drawable.ic_video_rec))
|
|
}
|
|
mPreview?.isImageCaptureIntent = isImageCaptureIntent()
|
|
}
|
|
|
|
private fun isImageCaptureIntent() = intent?.action == MediaStore.ACTION_IMAGE_CAPTURE || intent?.action == MediaStore.ACTION_IMAGE_CAPTURE_SECURE
|
|
|
|
private fun initializeCamera() {
|
|
setContentView(R.layout.activity_main)
|
|
initButtons()
|
|
|
|
(btn_holder.layoutParams as RelativeLayout.LayoutParams).setMargins(0, 0, 0, (navBarHeight + resources.getDimension(R.dimen.activity_margin)).toInt())
|
|
|
|
mCurrCameraId = config.lastUsedCamera
|
|
mPreview = Preview(this, camera_view, this)
|
|
mPreview!!.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
|
view_holder.addView(mPreview)
|
|
toggle_camera.setImageResource(if (mCurrCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) R.drawable.ic_camera_front else R.drawable.ic_camera_rear)
|
|
|
|
mFocusRectView = FocusRectView(applicationContext)
|
|
view_holder.addView(mFocusRectView)
|
|
|
|
mIsInPhotoMode = true
|
|
mTimerHandler = Handler()
|
|
mFadeHandler = Handler()
|
|
mFlashlightState = if (config.turnFlashOffAtStartup) FLASH_OFF else config.flashlightState
|
|
setupPreviewImage(true)
|
|
}
|
|
|
|
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()) {
|
|
return
|
|
}
|
|
|
|
mCurrCameraId = if (mCurrCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
|
|
Camera.CameraInfo.CAMERA_FACING_FRONT
|
|
} else {
|
|
Camera.CameraInfo.CAMERA_FACING_BACK
|
|
}
|
|
|
|
config.lastUsedCamera = mCurrCameraId
|
|
var newIconId = R.drawable.ic_camera_front
|
|
mPreview?.releaseCamera()
|
|
if (mPreview?.setCamera(mCurrCameraId) == true) {
|
|
if (mCurrCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
|
newIconId = R.drawable.ic_camera_rear
|
|
}
|
|
toggle_camera.setImageResource(newIconId)
|
|
disableFlash()
|
|
hideTimer()
|
|
} else {
|
|
toast(R.string.camera_switch_error)
|
|
}
|
|
}
|
|
|
|
private fun showLastMediaPreview() {
|
|
if (mPreviewUri != null) {
|
|
val path = applicationContext.getRealPathFromURI(mPreviewUri!!) ?: mPreviewUri!!.toString()
|
|
openPathIntent(path, false, BuildConfig.APPLICATION_ID)
|
|
}
|
|
}
|
|
|
|
private fun toggleFlash() {
|
|
if (!checkCameraAvailable()) {
|
|
return
|
|
}
|
|
|
|
mFlashlightState = ++mFlashlightState % if (mIsInPhotoMode) 3 else 2
|
|
checkFlash()
|
|
}
|
|
|
|
private fun checkFlash() {
|
|
when (mFlashlightState) {
|
|
FLASH_ON -> enableFlash()
|
|
FLASH_AUTO -> autoFlash()
|
|
else -> disableFlash()
|
|
}
|
|
}
|
|
|
|
private fun disableFlash() {
|
|
mPreview?.disableFlash()
|
|
toggle_flash.setImageResource(R.drawable.ic_flash_off)
|
|
mFlashlightState = FLASH_OFF
|
|
config.flashlightState = FLASH_OFF
|
|
}
|
|
|
|
private fun enableFlash() {
|
|
mPreview?.enableFlash()
|
|
toggle_flash.setImageResource(R.drawable.ic_flash_on)
|
|
mFlashlightState = FLASH_ON
|
|
config.flashlightState = FLASH_ON
|
|
}
|
|
|
|
private fun autoFlash() {
|
|
mPreview?.autoFlash()
|
|
toggle_flash.setImageResource(R.drawable.ic_flash_auto)
|
|
mFlashlightState = FLASH_AUTO
|
|
config.flashlightState = FLASH_AUTO
|
|
}
|
|
|
|
private fun shutterPressed() {
|
|
if (checkCameraAvailable()) {
|
|
handleShutter()
|
|
}
|
|
}
|
|
|
|
private fun handleShutter() {
|
|
if (mIsInPhotoMode) {
|
|
toggleBottomButtons(true)
|
|
mPreview?.tryTakePicture()
|
|
} else {
|
|
if (mPreview?.toggleRecording() == true) {
|
|
shutter.setImageDrawable(resources.getDrawable(R.drawable.ic_video_stop))
|
|
toggle_camera.beInvisible()
|
|
showTimer()
|
|
} else {
|
|
shutter.setImageDrawable(resources.getDrawable(R.drawable.ic_video_rec))
|
|
showToggleCameraIfNeeded()
|
|
hideTimer()
|
|
}
|
|
}
|
|
}
|
|
|
|
fun toggleBottomButtons(hide: Boolean) {
|
|
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
|
|
}
|
|
|
|
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 (mIsVideoCaptureIntent) {
|
|
finish()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun togglePhotoVideo() {
|
|
if (!checkCameraAvailable()) {
|
|
return
|
|
}
|
|
|
|
if (mIsVideoCaptureIntent)
|
|
mPreview?.trySwitchToVideo()
|
|
|
|
disableFlash()
|
|
hideTimer()
|
|
mIsInPhotoMode = !mIsInPhotoMode
|
|
showToggleCameraIfNeeded()
|
|
checkButtons()
|
|
toggleBottomButtons(false)
|
|
}
|
|
|
|
private fun checkButtons() {
|
|
if (mIsInPhotoMode) {
|
|
initPhotoMode()
|
|
} else {
|
|
tryInitVideoMode()
|
|
}
|
|
}
|
|
|
|
private fun initPhotoMode() {
|
|
toggle_photo_video.setImageDrawable(resources.getDrawable(R.drawable.ic_video))
|
|
shutter.setImageDrawable(resources.getDrawable(R.drawable.ic_shutter))
|
|
mPreview?.initPhotoMode()
|
|
setupPreviewImage(true)
|
|
}
|
|
|
|
private fun tryInitVideoMode() {
|
|
if (mPreview?.initRecorder() == true) {
|
|
initVideoButtons()
|
|
} else {
|
|
if (!mIsVideoCaptureIntent) {
|
|
toast(R.string.video_mode_error)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun initVideoButtons() {
|
|
toggle_photo_video.setImageDrawable(resources.getDrawable(R.drawable.ic_camera))
|
|
showToggleCameraIfNeeded()
|
|
shutter.setImageDrawable(resources.getDrawable(R.drawable.ic_video_rec))
|
|
checkFlash()
|
|
setupPreviewImage(false)
|
|
}
|
|
|
|
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())
|
|
|
|
runOnUiThread {
|
|
if (!isActivityDestroyed()) {
|
|
val options = RequestOptions()
|
|
.centerCrop()
|
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
|
|
Glide.with(this)
|
|
.load(mPreviewUri)
|
|
.apply(options)
|
|
.transition(DrawableTransitionOptions.withCrossFade())
|
|
.into(last_photo_video_preview)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun scheduleFadeOut() {
|
|
if (!config.keepSettingsVisible)
|
|
mFadeHandler.postDelayed({ fadeOutButtons() }, FADE_DELAY.toLong())
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
private fun hideNavigationBarIcons() {
|
|
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE
|
|
}
|
|
|
|
private fun hideTimer() {
|
|
video_rec_curr_timer.text = 0.getFormattedDuration()
|
|
video_rec_curr_timer.beGone()
|
|
mCurrVideoRecTimer = 0
|
|
mTimerHandler.removeCallbacksAndMessages(null)
|
|
}
|
|
|
|
private fun showTimer() {
|
|
video_rec_curr_timer.beVisible()
|
|
setupTimer()
|
|
}
|
|
|
|
private fun setupTimer() {
|
|
runOnUiThread(object : Runnable {
|
|
override fun run() {
|
|
video_rec_curr_timer.text = mCurrVideoRecTimer++.getFormattedDuration()
|
|
mTimerHandler.postDelayed(this, 1000)
|
|
}
|
|
})
|
|
}
|
|
|
|
private fun resumeCameraItems() {
|
|
showToggleCameraIfNeeded()
|
|
if (mPreview?.setCamera(mCurrCameraId) == true) {
|
|
hideNavigationBarIcons()
|
|
checkFlash()
|
|
|
|
if (!mIsInPhotoMode) {
|
|
initVideoButtons()
|
|
}
|
|
} else {
|
|
toast(R.string.camera_switch_error)
|
|
}
|
|
}
|
|
|
|
private fun showToggleCameraIfNeeded() {
|
|
toggle_camera?.beInvisibleIf(Camera.getNumberOfCameras() <= 1)
|
|
}
|
|
|
|
private fun hasStorageAndCameraPermissions() = hasPermission(PERMISSION_WRITE_STORAGE) && hasPermission(PERMISSION_CAMERA)
|
|
|
|
private fun setupOrientationEventListener() {
|
|
mOrientationEventListener = object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) {
|
|
override fun onOrientationChanged(orientation: Int) {
|
|
if (isActivityDestroyed()) {
|
|
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
|
|
mPreview?.deviceOrientationChanged()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
fun finishActivity() {
|
|
setResult(Activity.RESULT_OK)
|
|
finish()
|
|
}
|
|
|
|
override fun setFlashAvailable(available: Boolean) {
|
|
if (available) {
|
|
toggle_flash.beVisible()
|
|
} else {
|
|
toggle_flash.beInvisible()
|
|
disableFlash()
|
|
}
|
|
}
|
|
|
|
override fun setIsCameraAvailable(available: Boolean) {
|
|
mIsCameraAvailable = available
|
|
}
|
|
|
|
override fun videoSaved(uri: Uri) {
|
|
setupPreviewImage(mIsInPhotoMode)
|
|
if (mIsVideoCaptureIntent) {
|
|
Intent().apply {
|
|
data = uri
|
|
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
setResult(Activity.RESULT_OK, this)
|
|
}
|
|
finish()
|
|
}
|
|
}
|
|
|
|
override fun drawFocusRect(x: Int, y: Int) = mFocusRectView.drawFocusRect(x, y)
|
|
|
|
override fun mediaSaved(path: String) {
|
|
scanPath(path) {
|
|
setupPreviewImage(mIsInPhotoMode)
|
|
Intent(BROADCAST_REFRESH_MEDIA).apply {
|
|
`package` = "com.simplemobiletools.gallery"
|
|
sendBroadcast(this)
|
|
}
|
|
}
|
|
|
|
if (isImageCaptureIntent()) {
|
|
finishActivity()
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|