javaandroidandroid-camera2camera-api

Android Camera2 - Callback to Intercept E/Camera Error When It Becomes Unavailable (i.e. User Takes Selfie)


In my application I am using the Camera2 API to do some processing in the background. I am using the well known Camera2Basic (albeit now heavily modified). Everything runs great, but there is an obvious issue - when the user requests the camera for something (like launching a camera app to take a photo), my Camera2 quits, please see the LOGCAT:

E/Camera: Error 2
I/RequestThread-1: Flushing all pending requests.
I/RequestQueue: Repeating capture request cancelled.
I/CameraDeviceState: Legacy camera service transitioning to state ERROR
E/RequestQueue: cancel failed: no repeating request exists for request id: 0
E/CameraDeviceState: Cannot receive result while in state: 0
W/CaptureCollector: previewProduced called with no preview request on queue!
W/MessageQueue: Handler (android.os.Handler) {32bb202} sending message to a Handler on a dead thread
                                                                  java.lang.IllegalStateException: Handler (android.os.Handler) {32bb202} sending message to a Handler on a dead thread
                                                                  ... (irrelevant) ...
                                                                      at android.os.Looper.loop(Looper.java:154)
                                                                      at android.os.HandlerThread.run(HandlerThread.java:61)
E/MyApp: disconnected
D/gralloc: gralloc_lock_ycbcr success. format : 11, usage: 3, ycbcr.y: 0x66903000, .cb: 0x66afd401, .cr: 0x66afd400, .ystride: 1920 , .cstride: 1920, .chroma_step: 2
E/BufferItemConsumer: [ImageReader-1920x1080f23m1-19683-0] Failed to release buffer: Unknown error -1 (1)
E/MyApp: closed

Now... in Camera1 API I was able to intercept and act precisely at the moment the E/Camera Error 2 was reported, using this:

mCamera.setErrorCallback(errorCallback);

But in Camera2 API the situation is funny because even though I have configured my StateCallback (please notice the Log.e() correspond to the above LOGCAT output), the onError() doesn't fire at all and onClosed() and onDisconnected() trigger too late (!):

private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(@NonNull CameraDevice cameraDevice) {
        mCameraOpenCloseLock.release();
        mCameraDevice = cameraDevice;
        createCameraPreviewSession();
    }
    @Override
    public void onDisconnected(@NonNull CameraDevice cameraDevice) {
        mCameraOpenCloseLock.release();
        cameraDevice.close();
        mCameraDevice = null;
        Log.e("MyApp", "disconnected");
    }
    @Override
    public void onClosed(@NonNull CameraDevice cameraDevice) {
        super.onClosed(cameraDevice);
        Log.e("MyApp", "closed");
    }

    @Override
    public void onError(@NonNull CameraDevice cameraDevice, int error) {
        Log.e("MyApp", "Here we are "+error);
        mCameraOpenCloseLock.release();
        cameraDevice.close();
        mCameraDevice = null;
    }
};

Why wouldn't onError() trigger in case the camera becomes taken over by a user's request? How can I detect such a condition? I think that being able to "tap into" any of the following events would do the trick:

I/CameraDeviceState: Legacy camera service transitioning to state ERROR
E/RequestQueue: cancel failed: no repeating request exists for request id: 0
E/CameraDeviceState: Cannot receive result while in state: 0

Thank you kindly for any feedback. M.


Solution

  • Okay, so investigating this further I have arrived at a solution. The problem is that "Legacy" devices, which natively support Camera 1 API, although they "work" with Camera 2 API, behave differently than "Limited" / "Full" devices that natively support Camera 2 API.

    To detect the type of device you can use this snippet:

    CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
    Integer deviceLevel = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); // 0 - limited, 1 - full, 2 - legacy, 3 - uber full
    

    Now, "Limited" an "Full" devices will behave as expected and upon the camera becoming unavailable, the onDisconnect() event of the StateCallback triggers, so you can react. For "Legacy" devices, at least according to my tests, this happens too late (as per the LOGCAT in the original question).

    The solution seems to be to switch between Camera API's depending on the hardware level, and then use the mCamera.setErrorCallback(errorCallback); to catch the error on Camera 1 API devices. Like so:

    CameraErrorCallback errorCallback = new CameraErrorCallback();
    @SuppressWarnings("deprecation")
    public class CameraErrorCallback implements android.hardware.Camera.ErrorCallback {
        @Override
        public void onError(int error, android.hardware.Camera camera) {
        // Do something.
        }
    }