package com.simplemobiletools.camera.activities import android.app.Activity import android.content.Intent import android.content.res.Resources import android.hardware.Camera import android.hardware.SensorManager import android.net.Uri import android.os.Build 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.* import com.simplemobiletools.camera.Preview.PreviewListener import com.simplemobiletools.camera.extensions.config import com.simplemobiletools.camera.extensions.navBarHeight import com.simplemobiletools.camera.views.FocusRectView import com.simplemobiletools.commons.extensions.* 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 { companion object { private val FADE_DELAY = 5000 lateinit var mFocusRectView: FocusRectView lateinit var mTimerHandler: Handler lateinit var mFadeHandler: Handler lateinit var mRes: Resources 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?) { super.onCreate(savedInstanceState) 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() storeStoragePaths() 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() } private fun initVariables() { mRes = resources 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.visibility = View.GONE settings.visibility = View.GONE } private fun tryInitCamera() { handlePermission(PERMISSION_CAMERA) { if (it) { handlePermission(PERMISSION_WRITE_STORAGE) { if (it) { initializeCamera() handleIntent() } else { toast(R.string.no_permissions) finish() } } } else { toast(R.string.no_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(mRes.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 + mRes.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) { openFile(mPreviewUri!!, 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(mRes.getDrawable(R.drawable.ic_video_stop)) toggle_camera.beInvisible() showTimer() } else { shutter.setImageDrawable(mRes.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(mRes.getDrawable(R.drawable.ic_video)) shutter.setImageDrawable(mRes.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(mRes.getDrawable(R.drawable.ic_camera)) showToggleCameraIfNeeded() shutter.setImageDrawable(mRes.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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1 || !isDestroyed) { 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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && 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 mPreview?.deviceOrientationChanged() } } } } private fun animateViews(degrees: Int) { val views = arrayOf(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) } if (isImageCaptureIntent()) { finishActivity() } } private fun checkWhatsNewDialog() { arrayListOf().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)) checkWhatsNew(this, BuildConfig.VERSION_CODE) } } }