blob: e709702127a4d1bde88eebfe55e047146e48a1a8 [file] [log] [blame]
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.camera.camera2.pipe
import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraAccessException.CAMERA_DISABLED
import android.hardware.camera2.CameraAccessException.CAMERA_DISCONNECTED
import android.hardware.camera2.CameraAccessException.CAMERA_ERROR
import android.hardware.camera2.CameraAccessException.CAMERA_IN_USE
import android.hardware.camera2.CameraAccessException.MAX_CAMERAS_IN_USE
import android.hardware.camera2.CameraDevice.StateCallback
import android.os.Build
import androidx.annotation.RestrictTo
import androidx.camera.camera2.pipe.core.Log
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@JvmInline
value class CameraError private constructor(val value: Int) {
companion object {
/**
* Convenient placeholder for errors like CameraAccessException.CAMERA_ERROR where the cause
* of the error will be determined later through onError().
*/
val ERROR_UNDETERMINED = CameraError(0)
/**
* Indicates that the camera is in use. This can occur when:
* - Camera is in use by another app or process.
* - Camera is in use by a higher priority process
*/
val ERROR_CAMERA_IN_USE = CameraError(1)
/**
* The system-wide limit for number of open cameras or camera resources has been reached.
* - This can happen on devices that allow the app to open multiple camera devices. If the
* number of cameras has reached the system-wide limit, and the app attempts to open
* another one, this error can occur.
*/
val ERROR_CAMERA_LIMIT_EXCEEDED = CameraError(2)
/**
* The camera is disabled and cannot be opened. This can occur when:
* - Camera(s) are disabled due to security policy on the device.
* - Camera(s) are disabled because the app is not considered "foreground".
*/
val ERROR_CAMERA_DISABLED = CameraError(3)
/**
* The camera device has encountered a fatal error. This can occur when:
* - A critical error took place in the camera HAL.
* - A critical error in the driver or the underlying hardware.
*/
val ERROR_CAMERA_DEVICE = CameraError(4)
/**
* The camera service (framework) has encountered a fatal error. This can occur when:
* - There are issues within the camera service itself.
* - Camera service received erroneous data from the camera HAL.
* - We have Native-level crashes that brought down the camera service.
* - There are persistent hardware issues preventing the service from running.
*/
val ERROR_CAMERA_SERVICE = CameraError(5)
/**
* The camera has been disconnected for one of the following potential causes:
* - The camera has been disconnected from the Android device.
* - The camera ID is no longer valid.
* - The camera has been taken for a higher-priority process.
*/
val ERROR_CAMERA_DISCONNECTED = CameraError(6)
/** This indicates that we received IllegalArgumentException while opening the camera. */
val ERROR_ILLEGAL_ARGUMENT_EXCEPTION = CameraError(7)
/** This indicates that we received SecurityException while opening the camera. */
val ERROR_SECURITY_EXCEPTION = CameraError(8)
/**
* This indicates we've encountered an error while configuring our camera graph, such as
* creating, finalizing capture sessions, and creating, submitting capture requests.
*/
val ERROR_GRAPH_CONFIG = CameraError(9)
/**
* The camera cannot be opened because Do Not Disturb (DND) mode is on. This is actually a
* quirk for legacy devices on P, where we encounter RuntimeExceptions while opening the
* camera when it tries to enable shutter sound.
*/
val ERROR_DO_NOT_DISTURB_ENABLED = CameraError(10)
/**
* The CameraManager.openCamera() call threw an undocumented Exception. This can happen on
* camera devices that encountered an unhandled error, and thereby throwing an unknown
* Exception.
*/
val ERROR_UNKNOWN_EXCEPTION = CameraError(11)
/**
* The internal camera manager at CameraPipe has encountered an error, and isn't able to
* handle the incoming camera request.
*/
val ERROR_CAMERA_OPENER = CameraError(12)
internal fun from(throwable: Throwable) =
when (throwable) {
is CameraAccessException -> from(throwable)
is IllegalArgumentException -> ERROR_ILLEGAL_ARGUMENT_EXCEPTION
is SecurityException -> ERROR_SECURITY_EXCEPTION
else -> {
if (shouldHandleDoNotDisturbException(throwable)) {
ERROR_DO_NOT_DISTURB_ENABLED
} else {
Log.warn { "Unexpected throwable: $throwable" }
ERROR_UNKNOWN_EXCEPTION
}
}
}
internal fun from(exception: CameraAccessException) =
when (exception.reason) {
CAMERA_DISABLED -> ERROR_CAMERA_DISABLED
CAMERA_DISCONNECTED -> ERROR_CAMERA_DISCONNECTED
CAMERA_ERROR -> ERROR_UNDETERMINED
CAMERA_IN_USE -> ERROR_CAMERA_IN_USE
MAX_CAMERAS_IN_USE -> ERROR_CAMERA_LIMIT_EXCEEDED
else -> {
throw IllegalArgumentException(
"Unexpected CameraAccessException reason:" + "${exception.reason}"
)
}
}
internal fun from(stateCallbackError: Int) =
when (stateCallbackError) {
StateCallback.ERROR_CAMERA_IN_USE -> ERROR_CAMERA_IN_USE
StateCallback.ERROR_MAX_CAMERAS_IN_USE -> ERROR_CAMERA_LIMIT_EXCEEDED
StateCallback.ERROR_CAMERA_DISABLED -> ERROR_CAMERA_DISABLED
StateCallback.ERROR_CAMERA_DEVICE -> ERROR_CAMERA_DEVICE
StateCallback.ERROR_CAMERA_SERVICE -> ERROR_CAMERA_SERVICE
else -> {
throw IllegalArgumentException(
"Unexpected StateCallback error code: $stateCallbackError"
)
}
}
internal fun shouldHandleDoNotDisturbException(throwable: Throwable): Boolean =
Build.VERSION.SDK_INT == 28 && isDoNotDisturbException(throwable)
/**
* The full stack trace of the Do Not Disturb exception on API level 28 is as follows:
*
* java.lang.RuntimeException: Camera is being used after Camera.release() was called at
* android.hardware.Camera._enableShutterSound(Native Method) at
* android.hardware.Camera.updateAppOpsPlayAudio(Camera.java:1770) at
* android.hardware.Camera.initAppOps(Camera.java:582) at
* android.hardware.Camera.<init>(Camera.java:575) at
* android.hardware.Camera.getEmptyParameters(Camera.java:2130) at
* android.hardware.camera2.legacy.LegacyMetadataMapper.createCharacteristics
* (LegacyMetadataMapper.java:151) at
* android.hardware.camera2.CameraManager.getCameraCharacteristics (CameraManager.java:274)
*
* This function checks whether the method name of the top element is "_enableShutterSound".
*/
private fun isDoNotDisturbException(throwable: Throwable): Boolean {
if (throwable !is RuntimeException) return false
val topMethodName =
throwable.stackTrace.let { if (it.isNotEmpty()) it[0].methodName else null }
return topMethodName == "_enableShutterSound"
}
}
override fun toString(): String = "CameraError($value)"
}
// TODO(b/276918807): When we have CameraProperties, handle the exception on a more granular level.
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class DoNotDisturbException(message: String) : RuntimeException(message)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
// An exception indicating that the CameraDevice.close() call has stalled.
class CameraCloseStallException(message: String) : RuntimeException(message)