diff --git a/app/build.gradle b/app/build.gradle index 8f87a484..3d086b5d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,7 +69,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1" implementation 'androidx.window:window:1.1.0-alpha03' - def camerax_version = '1.2.0-rc01' + def camerax_version = '1.2.0-beta01' implementation "androidx.camera:camera-core:$camerax_version" implementation "androidx.camera:camera-camera2:$camerax_version" implementation "androidx.camera:camera-video:$camerax_version" diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt index 209ad740..7699ae89 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt @@ -6,11 +6,8 @@ import android.content.Intent import android.content.res.ColorStateList import android.graphics.Bitmap import android.hardware.SensorManager -import android.hardware.camera2.CameraCharacteristics 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.LinearLayout @@ -40,11 +37,11 @@ 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 kotlin.math.abs import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.layout_flash.* import kotlinx.android.synthetic.main.layout_top.* +import java.util.concurrent.TimeUnit +import kotlin.math.abs class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, CameraXPreviewListener { private companion object { @@ -54,7 +51,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera private const val MIN_SWIPE_DISTANCE_X = 100 } - lateinit var mTimerHandler: Handler private lateinit var defaultScene: Scene private lateinit var flashModeScene: Scene private lateinit var mOrientationEventListener: OrientationEventListener @@ -62,15 +58,26 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera private var mPreview: MyPreview? = null private var mediaSizeToggleGroup: MaterialButtonToggleGroup? = 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 + private var mLastHandledOrientation = 0 private val tabSelectedListener = object : TabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab) { - handleTogglePhotoVideo() + handlePermission(PERMISSION_RECORD_AUDIO) { + if (it) { + when (tab.position) { + VIDEO_MODE_INDEX -> mPreview?.initVideoMode() + PHOTO_MODE_INDEX -> mPreview?.initPhotoMode() + else -> throw IllegalStateException("Unsupported tab position ${tab.position}") + } + } else { + toast(R.string.no_audio_permissions) + selectPhotoTab() + if (isVideoCaptureIntent()) { + finish() + } + } + } } } @@ -79,7 +86,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera super.onCreate(savedInstanceState) appLaunched(BuildConfig.APPLICATION_ID) requestWindowFeature(Window.FEATURE_NO_TITLE) - initVariables() tryInitCamera() supportActionBar?.hide() @@ -107,6 +113,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera if (!triggerListener) { removeTabListener() } + camera_mode_tab.getTabAt(PHOTO_MODE_INDEX)?.select() setTabListener() } @@ -130,17 +137,13 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera override fun onResume() { super.onResume() if (hasStorageAndCameraPermissions()) { - resumeCameraItems() - setupPreviewImage(mIsInPhotoMode) + val isInPhotoMode = isInPhotoMode() + setupPreviewImage(isInPhotoMode) mFocusCircleView.setStrokeColor(getProperPrimaryColor()) - - if (isVideoCaptureIntent() && mIsInPhotoMode) { - handleTogglePhotoVideo() - checkButtons() - } toggleBottomButtons(enabled = true) mOrientationEventListener.enable() } + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) ensureTransparentNavigationBar() } @@ -156,7 +159,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera return } - hideTimer() mOrientationEventListener.disable() } @@ -172,18 +174,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } private fun initVariables() { - mIsInPhotoMode = if (isVideoCaptureIntent()) { - false - } else if (isImageCaptureIntent()) { - true - } else { - config.initPhotoMode - } - mIsCameraAvailable = false mIsHardwareShutterHandled = false - mCurrVideoRecTimer = 0 - mLastHandledOrientation = 0 - config.lastUsedCamera = CameraCharacteristics.LENS_FACING_BACK.toString() } override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { @@ -218,16 +209,22 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera if (grantedCameraPermission) { handleStoragePermission { grantedStoragePermission -> if (grantedStoragePermission) { - if (mIsInPhotoMode) { - initializeCamera() + val isInPhotoMode = isInPhotoMode() + if (isInPhotoMode) { + initializeCamera(true) } else { handlePermission(PERMISSION_RECORD_AUDIO) { grantedRecordAudioPermission -> if (grantedRecordAudioPermission) { - initializeCamera() + initializeCamera(false) } else { toast(R.string.no_audio_permissions) - togglePhotoVideoMode() - tryInitCamera() + if (isThirdPartyIntent()) { + finish() + } else { + // re-initialize in photo mode + config.initPhotoMode = true + tryInitCamera() + } } } } @@ -243,6 +240,16 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } } + private fun isInPhotoMode(): Boolean { + return mPreview?.isInPhotoMode() ?: if (isVideoCaptureIntent()) { + false + } else if (isImageCaptureIntent()) { + true + } else { + config.initPhotoMode + } + } + private fun handleStoragePermission(callback: (granted: Boolean) -> Unit) { if (isTiramisuPlus()) { handlePermission(PERMISSION_READ_MEDIA_IMAGES) { grantedReadImages -> @@ -263,24 +270,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera 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 (isVideoCaptureIntent()) { - mIsInPhotoMode = false - hideIntentButtons() - shutter.setImageResource(R.drawable.ic_video_rec_vector) - } - } - private fun createToggleGroup(): MaterialButtonToggleGroup { return MaterialButtonToggleGroup(this).apply { isSingleSelection = true @@ -288,7 +277,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } } - private fun initializeCamera() { + private fun initializeCamera(isInPhotoMode: Boolean) { setContentView(R.layout.activity_main) initButtons() initModeSwitcher() @@ -313,8 +302,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera WindowInsetsCompat.CONSUMED } - checkVideoCaptureIntent() - if (mIsInPhotoMode) { + if (isInPhotoMode) { selectPhotoTab() } else { selectVideoTab() @@ -327,25 +315,18 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera listener = this, outputUri = outputUri, isThirdPartyIntent = isThirdPartyIntent, - initInPhotoMode = mIsInPhotoMode, + initInPhotoMode = isInPhotoMode, ) - checkImageCaptureIntent() - mPreview?.setIsImageCaptureIntent(isImageCaptureIntent()) - val imageDrawable = if (config.lastUsedCamera == CameraCharacteristics.LENS_FACING_BACK.toString()) { - R.drawable.ic_camera_front_vector - } else { - R.drawable.ic_camera_rear_vector - } - - toggle_camera.setImageResource(imageDrawable) - - mFocusCircleView = FocusCircleView(applicationContext) + mFocusCircleView = FocusCircleView(this) view_holder.addView(mFocusCircleView) - mTimerHandler = Handler(Looper.getMainLooper()) setupPreviewImage(true) initFlashModeTransitionNames() + + if (isThirdPartyIntent) { + hideIntentButtons() + } } private fun initFlashModeTransitionNames() { @@ -357,9 +338,9 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } private fun initButtons() { - toggle_camera.setOnClickListener { toggleCamera() } + toggle_camera.setOnClickListener { mPreview!!.toggleFrontBackCamera() } last_photo_video_preview.setOnClickListener { showLastMediaPreview() } - toggle_flash.setOnClickListener { toggleFlash() } + toggle_flash.setOnClickListener { mPreview!!.handleFlashlightClick() } shutter.setOnClickListener { shutterPressed() } settings.setShadowIcon(R.drawable.ic_settings_vector) @@ -382,7 +363,13 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera @SuppressLint("ClickableViewAccessibility") private fun initModeSwitcher() { - val gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() { + val gestureDetector = GestureDetectorCompat(this, object : GestureDetector.SimpleOnGestureListener() { + override fun onDown(e: MotionEvent): Boolean { + // we have to return true here so ACTION_UP + // (and onFling) can be dispatched + return true + } + override fun onFling(event1: MotionEvent, event2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { // these can be null even if the docs say they cannot, getting event1.x in itself can cause crashes try { @@ -430,11 +417,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera mPreview?.setFlashlightState(flashMode) } - private fun toggleCamera() { - if (checkCameraAvailable()) { - mPreview!!.toggleFrontBackCamera() - } - } private fun showLastMediaPreview() { if (mPreviewUri != null) { @@ -443,24 +425,8 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } } - private fun toggleFlash() { - if (checkCameraAvailable()) { - if (mIsInPhotoMode) { - showFlashOptions(true) - } else { - mPreview?.toggleFlashlight() - } - } - } - private fun shutterPressed() { - if (checkCameraAvailable()) { - handleShutter() - } - } - - private fun handleShutter() { - if (mIsInPhotoMode) { + if (isInPhotoMode()) { toggleBottomButtons(enabled = false) change_resolution.isEnabled = true mPreview?.tryTakePicture() @@ -474,71 +440,15 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera startActivity(intent) } - private fun handleTogglePhotoVideo() { - handlePermission(PERMISSION_RECORD_AUDIO) { - if (it) { - togglePhotoVideo() - } else { - toast(R.string.no_audio_permissions) - selectPhotoTab() - if (isVideoCaptureIntent()) { - finish() - } - } - } - } - - private fun togglePhotoVideo() { - if (!checkCameraAvailable()) { - return - } - - if (isVideoCaptureIntent()) { - mPreview?.initVideoMode() - } - - mPreview?.setFlashlightState(FLASH_OFF) - hideTimer() - togglePhotoVideoMode() - checkButtons() - toggleBottomButtons(enabled = true) - } - - private fun togglePhotoVideoMode() { - mIsInPhotoMode = !mIsInPhotoMode - config.initPhotoMode = mIsInPhotoMode - } - - private fun checkButtons() { - if (mIsInPhotoMode) { - initPhotoMode() - } else { - tryInitVideoMode() - } - } - - private fun initPhotoMode() { + override fun onInitPhotoMode() { shutter.setImageResource(R.drawable.ic_shutter_animated) - mPreview?.initPhotoMode() setupPreviewImage(true) selectPhotoTab() } - private fun tryInitVideoMode() { - try { - mPreview?.initVideoMode() - initVideoButtons() - } catch (e: Exception) { - if (!isVideoCaptureIntent()) { - toast(R.string.video_mode_error) - } - } - } - - private fun initVideoButtons() { + override fun onInitVideoMode() { shutter.setImageResource(R.drawable.ic_video_rec_animated) setupPreviewImage(false) - mPreview?.checkFlashlight() selectVideoTab() } @@ -571,21 +481,8 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } } - private fun hideTimer() { - video_rec_curr_timer.text = 0.getFormattedDuration() - video_rec_curr_timer.beGone() - mCurrVideoRecTimer = 0 - mTimerHandler.removeCallbacksAndMessages(null) - } - - private fun resumeCameraItems() { - if (!mIsInPhotoMode) { - initVideoButtons() - } - } - private fun hasStorageAndCameraPermissions(): Boolean { - return if (mIsInPhotoMode) hasPhotoModePermissions() else hasVideoModePermissions() + return if (isInPhotoMode()) hasPhotoModePermissions() else hasVideoModePermissions() } private fun hasPhotoModePermissions(): Boolean { @@ -641,17 +538,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera 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) } @@ -792,11 +678,11 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera isFrontCamera: Boolean, onSelect: (index: Int, changed: Boolean) -> Unit ) { - top_options.removeView(mediaSizeToggleGroup) val mediaSizeToggleGroup = createToggleGroup().apply { mediaSizeToggleGroup = this } + top_options.addView(mediaSizeToggleGroup) val onItemClick = { clickedViewId: Int -> @@ -824,6 +710,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera val params = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT).apply { weight = 1f } + return (layoutInflater.inflate(R.layout.layout_button, null) as MaterialButton).apply { layoutParams = params setShadowIcon(resolutionOption.imageDrawableResId) diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt index 0652b069..98728de1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/helpers/Config.kt @@ -35,10 +35,6 @@ class Config(context: Context) : BaseConfig(context) { get() = prefs.getBoolean(FLIP_PHOTOS, true) set(flipPhotos) = prefs.edit().putBoolean(FLIP_PHOTOS, flipPhotos).apply() - var lastUsedCamera: String - get() = prefs.getString(LAST_USED_CAMERA, "0")!! - set(cameraId) = prefs.edit().putString(LAST_USED_CAMERA, cameraId).apply() - var lastUsedCameraLens: Int get() = prefs.getInt(LAST_USED_CAMERA_LENS, CameraSelector.LENS_FACING_BACK) set(lens) = prefs.edit().putInt(LAST_USED_CAMERA_LENS, lens).apply() diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXInitializer.kt b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXInitializer.kt index fce3bea2..4a28c7f8 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXInitializer.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXInitializer.kt @@ -23,7 +23,8 @@ class CameraXInitializer(private val activity: BaseSimpleActivity) { mediaOutputHelper, cameraErrorHandler, listener, - initInPhotoMode, + isThirdPartyIntent = isThirdPartyIntent, + initInPhotoMode = initInPhotoMode, ) } diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt index 07497ee8..1da6da7f 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreview.kt @@ -4,6 +4,8 @@ import android.annotation.SuppressLint import android.content.Context import android.hardware.SensorManager import android.hardware.display.DisplayManager +import android.os.Handler +import android.os.Looper import android.util.Rational import android.util.Size import android.view.* @@ -19,6 +21,7 @@ import androidx.camera.view.PreviewView.ScaleType import androidx.core.content.ContextCompat import androidx.core.view.doOnLayout import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.window.layout.WindowMetricsCalculator import com.bumptech.glide.load.ImageHeaderParser.UNKNOWN_ORIENTATION @@ -39,6 +42,7 @@ class CameraXPreview( private val mediaOutputHelper: MediaOutputHelper, private val cameraErrorHandler: CameraErrorHandler, private val listener: CameraXPreviewListener, + private val isThirdPartyIntent: Boolean, initInPhotoMode: Boolean, ) : MyPreview, DefaultLifecycleObserver { @@ -46,6 +50,7 @@ class CameraXPreview( // Auto focus is 1/6 of the area. private const val AF_SIZE = 1.0f / 6.0f private const val AE_SIZE = AF_SIZE * 1.5f + private const val CAMERA_MODE_SWITCH_WAIT_TIME = 500L } private val config = activity.config @@ -80,6 +85,29 @@ class CameraXPreview( } } } + private val startCameraHandler = Handler(Looper.getMainLooper()) + private val photoModeRunnable = Runnable { + if (imageCapture == null) { + isPhotoCapture = true + if (!isThirdPartyIntent) { // we don't want to store the state for 3rd party intents + config.initPhotoMode = true + } + startCamera() + } else { + listener.onInitPhotoMode() + } + } + private val videoModeRunnable = Runnable { + if (videoCapture == null) { + isPhotoCapture = false + if (!isThirdPartyIntent) { // we don't want to store the state for 3rd party intents + config.initPhotoMode = false + } + startCamera() + } else { + listener.onInitVideoMode() + } + } private var preview: Preview? = null private var cameraProvider: ProcessCameraProvider? = null @@ -92,13 +120,11 @@ class CameraXPreview( private var flashMode = FLASH_MODE_OFF private var isPhotoCapture = initInPhotoMode private var lastRotation = 0 + private var lastCameraStartTime = 0L init { bindToLifeCycle() mediaSoundHelper.loadSounds() - previewView.doOnLayout { - startCamera() - } } private fun bindToLifeCycle() { @@ -106,13 +132,12 @@ class CameraXPreview( } private fun startCamera(switching: Boolean = false) { - imageQualityManager.initSupportedQualities() - - val cameraProviderFuture = ProcessCameraProvider.getInstance(activity) + val cameraProviderFuture = ProcessCameraProvider.getInstance(activity.applicationContext) cameraProviderFuture.addListener({ try { val provider = cameraProviderFuture.get() cameraProvider = provider + imageQualityManager.initSupportedQualities() videoQualityManager.initSupportedQualities(provider) bindCameraUseCases() setupCameraObservers() @@ -128,11 +153,11 @@ class CameraXPreview( val resolution = if (isPhotoCapture) { imageQualityManager.getUserSelectedResolution(cameraSelector).also { - displaySelectedResolution(it.toResolutionOption()) + listener.displaySelectedResolution(it.toResolutionOption()) } } else { val selectedQuality = videoQualityManager.getUserSelectedQuality(cameraSelector).also { - displaySelectedResolution(it.toResolutionOption()) + listener.displaySelectedResolution(it.toResolutionOption()) } MySize(selectedQuality.width, selectedQuality.height) } @@ -178,10 +203,6 @@ class CameraXPreview( setFlashlightState(config.flashlightState) } - private fun displaySelectedResolution(resolutionOption: ResolutionOption) { - listener.displaySelectedResolution(resolutionOption) - } - private fun getRotatedResolution(resolution: MySize, rotationDegrees: Int): Size { return if (rotationDegrees == Surface.ROTATION_0 || rotationDegrees == Surface.ROTATION_180) { Size(resolution.height, resolution.width) @@ -201,10 +222,12 @@ class CameraXPreview( return if (isPhotoCapture) { buildImageCapture(resolution, rotation).also { imageCapture = it + videoCapture = null } } else { buildVideoCapture().also { videoCapture = it + imageCapture = null } } } @@ -242,22 +265,29 @@ class CameraXPreview( private fun setupCameraObservers() { listener.setFlashAvailable(camera?.cameraInfo?.hasFlashUnit() ?: false) listener.onChangeCamera(isFrontCameraInUse()) - + if (isPhotoCapture) { + listener.onInitPhotoMode() + } else { + listener.onInitVideoMode() + } camera?.cameraInfo?.cameraState?.observe(activity) { cameraState -> - when (cameraState.type) { - CameraState.Type.OPEN, - CameraState.Type.OPENING -> { - listener.setHasFrontAndBackCamera(hasFrontCamera() && hasBackCamera()) - listener.setCameraAvailable(true) - } - CameraState.Type.PENDING_OPEN, - CameraState.Type.CLOSING, - CameraState.Type.CLOSED -> { - listener.setCameraAvailable(false) + if (cameraState.error == null) { + when (cameraState.type) { + CameraState.Type.OPENING, + CameraState.Type.OPEN -> { + listener.setHasFrontAndBackCamera(hasFrontCamera() && hasBackCamera()) + listener.setCameraAvailable(true) + } + CameraState.Type.PENDING_OPEN, + CameraState.Type.CLOSING, + CameraState.Type.CLOSED -> { + listener.setCameraAvailable(false) + } } + } else { + listener.setCameraAvailable(false) + cameraErrorHandler.handleCameraError(cameraState.error) } - - cameraErrorHandler.handleCameraError(cameraState?.error) } } @@ -312,12 +342,21 @@ class CameraXPreview( override fun onStart(owner: LifecycleOwner) { orientationEventListener.enable() + previewView.doOnLayout { + if (owner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { + startCamera() + } + } } override fun onStop(owner: LifecycleOwner) { orientationEventListener.disable() } + override fun isInPhotoMode(): Boolean { + return isPhotoCapture + } + override fun showChangeResolution() { val selectedResolution = if (isPhotoCapture) { imageQualityManager.getUserSelectedResolution(cameraSelector).toResolutionOption() @@ -377,7 +416,15 @@ class CameraXPreview( startCamera(switching = true) } - override fun toggleFlashlight() { + override fun handleFlashlightClick() { + if (isPhotoCapture) { + listener.showFlashOptions(true) + } else { + toggleFlashlight() + } + } + + private fun toggleFlashlight() { val newFlashMode = if (isPhotoCapture) { when (flashMode) { FLASH_MODE_OFF -> FLASH_MODE_ON @@ -396,17 +443,22 @@ class CameraXPreview( } override fun setFlashlightState(state: Int) { + var flashState = state if (isPhotoCapture) { - camera?.cameraControl?.enableTorch(state == FLASH_ALWAYS_ON) + camera?.cameraControl?.enableTorch(flashState == FLASH_ALWAYS_ON) } else { - camera?.cameraControl?.enableTorch(state == FLASH_ON || state == FLASH_ALWAYS_ON) + camera?.cameraControl?.enableTorch(flashState == FLASH_ON || flashState == FLASH_ALWAYS_ON) + // reset to the FLASH_ON for video capture + if (flashState == FLASH_ALWAYS_ON) { + flashState = FLASH_ON + } } - val newFlashMode = state.toCameraXFlashMode() + val newFlashMode = flashState.toCameraXFlashMode() flashMode = newFlashMode imageCapture?.flashMode = newFlashMode - config.flashlightState = state - listener.onChangeFlashMode(state) + config.flashlightState = flashState + listener.onChangeFlashMode(flashState) } override fun tryTakePicture() { @@ -467,13 +519,23 @@ class CameraXPreview( } override fun initPhotoMode() { - isPhotoCapture = true - startCamera() + debounceChangeCameraMode(photoModeRunnable) } override fun initVideoMode() { - isPhotoCapture = false - startCamera() + debounceChangeCameraMode(videoModeRunnable) + } + + private fun debounceChangeCameraMode(cameraModeRunnable: Runnable) { + val currentTime = System.currentTimeMillis() + if (currentTime - lastCameraStartTime > CAMERA_MODE_SWITCH_WAIT_TIME) { + cameraModeRunnable.run() + } else { + startCameraHandler.removeCallbacks(photoModeRunnable) + startCameraHandler.removeCallbacks(videoModeRunnable) + startCameraHandler.postDelayed(cameraModeRunnable, CAMERA_MODE_SWITCH_WAIT_TIME) + } + lastCameraStartTime = currentTime } override fun toggleRecording() { @@ -489,8 +551,7 @@ class CameraXPreview( private fun startRecording() { val videoCapture = videoCapture ?: throw IllegalStateException("Camera initialization failed.") - val mediaOutput = mediaOutputHelper.getVideoMediaOutput() - val recording = when (mediaOutput) { + val recording = when (val mediaOutput = mediaOutputHelper.getVideoMediaOutput()) { is MediaOutput.FileDescriptorMediaOutput -> { FileDescriptorOutputOptions.Builder(mediaOutput.fileDescriptor).build() .let { videoCapture.output.prepareRecording(activity, it) } diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreviewListener.kt b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreviewListener.kt index 530d514a..5c9f1866 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreviewListener.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/implementations/CameraXPreviewListener.kt @@ -5,7 +5,9 @@ import android.net.Uri import com.simplemobiletools.camera.models.ResolutionOption interface CameraXPreviewListener { - fun setCameraAvailable(available: Boolean) + fun onInitPhotoMode() + fun onInitVideoMode() + fun setCameraAvailable(available: Boolean) {} fun setHasFrontAndBackCamera(hasFrontAndBack: Boolean) fun setFlashAvailable(available: Boolean) fun onChangeCamera(frontCamera: Boolean) diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt b/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt index 02447fc4..f3b770a8 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/interfaces/MyPreview.kt @@ -1,18 +1,14 @@ package com.simplemobiletools.camera.interfaces -import android.net.Uri - interface MyPreview { - fun setTargetUri(uri: Uri) = Unit + fun isInPhotoMode(): Boolean - fun setIsImageCaptureIntent(isImageCaptureIntent: Boolean) = Unit - - fun setFlashlightState(state: Int) = Unit + fun setFlashlightState(state: Int) fun toggleFrontBackCamera() - fun toggleFlashlight() = Unit + fun handleFlashlightClick() fun tryTakePicture() @@ -22,7 +18,5 @@ interface MyPreview { fun initVideoMode() - fun checkFlashlight() = Unit - - fun showChangeResolution() = Unit + fun showChangeResolution() }