handle some camera errors

- add CameraErrorHandler to handle
  - errors during camera lifecycle
  - when capturing images
  - when recording videos
This commit is contained in:
darthpaul 2022-06-26 21:54:32 +01:00
parent b10d8639fd
commit f43cd4f939
3 changed files with 62 additions and 50 deletions

View File

@ -0,0 +1,41 @@
package com.simplemobiletools.camera.helpers
import android.content.Context
import android.widget.Toast
import androidx.camera.core.CameraState
import androidx.camera.core.ImageCapture
import androidx.camera.video.VideoRecordEvent
import com.simplemobiletools.camera.R
import com.simplemobiletools.commons.extensions.toast
class CameraErrorHandler(
private val context: Context,
) {
fun handleCameraError(error: CameraState.StateError?) {
when (error?.code) {
CameraState.ERROR_MAX_CAMERAS_IN_USE,
CameraState.ERROR_CAMERA_IN_USE -> context.toast(R.string.camera_in_use_error, Toast.LENGTH_LONG)
CameraState.ERROR_CAMERA_FATAL_ERROR -> context.toast(R.string.camera_unavailable)
CameraState.ERROR_STREAM_CONFIG -> context.toast(R.string.camera_configure_error)
CameraState.ERROR_CAMERA_DISABLED -> context.toast(R.string.camera_disabled_by_admin_error)
CameraState.ERROR_DO_NOT_DISTURB_MODE_ENABLED -> context.toast(R.string.camera_dnd_error, Toast.LENGTH_LONG)
CameraState.ERROR_OTHER_RECOVERABLE_ERROR -> {}
}
}
fun handleImageCaptureError(imageCaptureError: Int) {
when (imageCaptureError) {
ImageCapture.ERROR_FILE_IO -> context.toast(R.string.photo_not_saved)
else -> context.toast(R.string.photo_capture_failed)
}
}
fun handleVideoRecordingError(error: Int) {
when (error) {
VideoRecordEvent.Finalize.ERROR_INSUFFICIENT_STORAGE -> context.toast(R.string.video_capture_insufficient_storage_error)
VideoRecordEvent.Finalize.ERROR_NONE -> {}
else -> context.toast(R.string.video_recording_failed)
}
}
}

View File

@ -55,13 +55,12 @@ import com.simplemobiletools.camera.R
import com.simplemobiletools.camera.extensions.config
import com.simplemobiletools.camera.extensions.toAppFlashMode
import com.simplemobiletools.camera.extensions.toCameraSelector
import com.simplemobiletools.camera.extensions.toCameraXFlashMode
import com.simplemobiletools.camera.extensions.toLensFacing
import com.simplemobiletools.camera.helpers.CameraErrorHandler
import com.simplemobiletools.camera.helpers.MediaOutputHelper
import com.simplemobiletools.camera.helpers.MediaSoundHelper
import com.simplemobiletools.camera.helpers.PinchToZoomOnScaleGestureListener
import com.simplemobiletools.camera.interfaces.MyPreview
import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.extensions.toast
import java.text.SimpleDateFormat
import java.util.Date
@ -93,6 +92,7 @@ class CameraXPreview(
private val displayManager = activity.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
private val mediaSoundHelper = MediaSoundHelper()
private val windowMetricsCalculator = WindowMetricsCalculator.getOrCreate()
private val cameraErrorHandler = CameraErrorHandler(activity)
private val orientationEventListener = object : OrientationEventListener(activity, SensorManager.SENSOR_DELAY_NORMAL) {
@SuppressLint("RestrictedApi")
@ -137,7 +137,7 @@ class CameraXPreview(
activity.lifecycle.addObserver(this)
}
private fun startCamera() {
private fun startCamera(switching: Boolean = false) {
Log.i(TAG, "startCamera: ")
val cameraProviderFuture = ProcessCameraProvider.getInstance(activity)
cameraProviderFuture.addListener({
@ -147,7 +147,7 @@ class CameraXPreview(
setupCameraObservers()
} catch (e: Exception) {
Log.e(TAG, "startCamera: ", e)
activity.showErrorToast(activity.getString(R.string.camera_open_error))
activity.toast(if (switching) R.string.camera_switch_error else R.string.camera_open_error)
}
}, mainExecutor)
}
@ -189,48 +189,7 @@ class CameraXPreview(
}
}
// TODO: Handle errors
cameraState.error?.let { error ->
listener.setCameraAvailable(false)
when (error.code) {
CameraState.ERROR_STREAM_CONFIG -> {
Log.e(TAG, "ERROR_STREAM_CONFIG")
// Make sure to setup the use cases properly
activity.toast(R.string.camera_unavailable)
}
CameraState.ERROR_CAMERA_IN_USE -> {
Log.e(TAG, "ERROR_CAMERA_IN_USE")
// Close the camera or ask user to close another camera app that's using the
// camera
activity.showErrorToast("Camera is in use by another app, please close")
}
CameraState.ERROR_MAX_CAMERAS_IN_USE -> {
Log.e(TAG, "ERROR_MAX_CAMERAS_IN_USE")
// Close another open camera in the app, or ask the user to close another
// camera app that's using the camera
activity.showErrorToast("Camera is in use by another app, please close")
}
CameraState.ERROR_OTHER_RECOVERABLE_ERROR -> {
Log.e(TAG, "ERROR_OTHER_RECOVERABLE_ERROR")
activity.toast(R.string.camera_open_error)
}
CameraState.ERROR_CAMERA_DISABLED -> {
Log.e(TAG, "ERROR_CAMERA_DISABLED")
// Ask the user to enable the device's cameras
activity.toast(R.string.camera_open_error)
}
CameraState.ERROR_CAMERA_FATAL_ERROR -> {
Log.e(TAG, "ERROR_CAMERA_FATAL_ERROR")
// Ask the user to reboot the device to restore camera function
activity.toast(R.string.camera_open_error)
}
CameraState.ERROR_DO_NOT_DISTURB_MODE_ENABLED -> {
// Ask the user to disable the "Do Not Disturb" mode, then reopen the camera
Log.e(TAG, "ERROR_DO_NOT_DISTURB_MODE_ENABLED")
activity.toast(R.string.camera_open_error)
}
}
}
cameraErrorHandler.handleCameraError(cameraState?.error)
}
}
@ -353,7 +312,7 @@ class CameraXPreview(
}
cameraSelector = newCameraSelector
config.lastUsedCameraLens = newCameraSelector.toLensFacing()
startCamera()
startCamera(switching = true)
}
override fun toggleFlashlight() {
@ -411,9 +370,9 @@ class CameraXPreview(
}
override fun onError(exception: ImageCaptureException) {
listener.toggleBottomButtons(false)
activity.showErrorToast("Capture picture $exception")
Log.e(TAG, "Error", exception)
listener.toggleBottomButtons(false)
cameraErrorHandler.handleImageCaptureError(exception.imageCaptureError)
}
})
playShutterSoundIfEnabled()
@ -477,7 +436,8 @@ class CameraXPreview(
playStopVideoRecordingSoundIfEnabled()
listener.onVideoRecordingStopped()
if (recordEvent.hasError()) {
// TODO: Handle errors
Log.e(TAG, "recording failed:", recordEvent.cause)
cameraErrorHandler.handleVideoRecordingError(recordEvent.error)
} else {
listener.onMediaCaptured(mediaOutput?.uri ?: recordEvent.outputResults.outputUri)
}

View File

@ -2,6 +2,8 @@
<resources>
<string name="app_name">Simple Camera</string>
<string name="app_launcher_name">Camera</string>
<!--Errors-->
<string name="camera_unavailable">Camera unavailable</string>
<string name="camera_open_error">An error occurred accessing the camera</string>
<string name="video_creating_error">An error occurred creating the video file</string>
@ -13,6 +15,15 @@
<string name="setting_resolution_failed">Setting proper resolution failed</string>
<string name="video_recording_failed">Video recording failed, try using a different resolution</string>
<!--TODO: Add strings in other locales-->
<string name="camera_in_use_error">Camera is in use by another app, please close the app and try again</string>
<string name="camera_configure_error">An error occurred while configuring the camera</string>
<string name="camera_disabled_by_admin_error">Camera is disabled by the admin</string>
<string name="camera_dnd_error">"Do Not Disturb" mode is enabled. Please disable and try again</string>
<string name="photo_capture_failed">Photo capture failed</string>
<string name="video_capture_insufficient_storage_error">Video recording failed due to insufficient storage</string>
<!-- FAQ -->
<string name="faq_1_title">What photo compression quality should I set?</string>
<string name="faq_1_text">It depends on your goal. For generic purposes most people advise using 75%-80%, when the image is still really good quality, but the file size is reduced drastically compared to 100%.</string>