| /* |
| * Copyright (C) 2014 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 android.hardware.camera2.legacy; |
| |
| import android.hardware.camera2.impl.CameraDeviceImpl; |
| import android.hardware.camera2.impl.CameraMetadataNative; |
| import android.os.Handler; |
| import android.util.Log; |
| |
| /** |
| * Emulates a the state of a single Camera2 device. |
| * |
| * <p> |
| * This class acts as the state machine for a camera device. Valid state transitions are given |
| * in the table below: |
| * </p> |
| * |
| * <ul> |
| * <li>{@code UNCONFIGURED -> CONFIGURING}</li> |
| * <li>{@code CONFIGURING -> IDLE}</li> |
| * <li>{@code IDLE -> CONFIGURING}</li> |
| * <li>{@code IDLE -> CAPTURING}</li> |
| * <li>{@code IDLE -> IDLE}</li> |
| * <li>{@code CAPTURING -> IDLE}</li> |
| * <li>{@code ANY -> ERROR}</li> |
| * </ul> |
| */ |
| public class CameraDeviceState { |
| private static final String TAG = "CameraDeviceState"; |
| private static final boolean DEBUG = false; |
| |
| private static final int STATE_ERROR = 0; |
| private static final int STATE_UNCONFIGURED = 1; |
| private static final int STATE_CONFIGURING = 2; |
| private static final int STATE_IDLE = 3; |
| private static final int STATE_CAPTURING = 4; |
| |
| private static final String[] sStateNames = { "ERROR", "UNCONFIGURED", "CONFIGURING", "IDLE", |
| "CAPTURING"}; |
| |
| private int mCurrentState = STATE_UNCONFIGURED; |
| private int mCurrentError = NO_CAPTURE_ERROR; |
| |
| private RequestHolder mCurrentRequest = null; |
| |
| private Handler mCurrentHandler = null; |
| private CameraDeviceStateListener mCurrentListener = null; |
| |
| /** |
| * Error code used by {@link #setCaptureStart} and {@link #setCaptureResult} to indicate that no |
| * error has occurred. |
| */ |
| public static final int NO_CAPTURE_ERROR = -1; |
| |
| /** |
| * CameraDeviceStateListener callbacks to be called after state transitions. |
| */ |
| public interface CameraDeviceStateListener { |
| void onError(int errorCode, Object errorArg, RequestHolder holder); |
| void onConfiguring(); |
| void onIdle(); |
| void onBusy(); |
| void onCaptureStarted(RequestHolder holder, long timestamp); |
| void onCaptureResult(CameraMetadataNative result, RequestHolder holder); |
| void onRequestQueueEmpty(); |
| void onRepeatingRequestError(long lastFrameNumber); |
| } |
| |
| /** |
| * Transition to the {@code ERROR} state. |
| * |
| * <p> |
| * The device cannot exit the {@code ERROR} state. If the device was not already in the |
| * {@code ERROR} state, {@link CameraDeviceStateListener#onError(int, RequestHolder)} will be |
| * called. |
| * </p> |
| * |
| * @param error the error to set. Should be one of the error codes defined in |
| * {@link CameraDeviceImpl.CameraDeviceCallbacks}. |
| */ |
| public synchronized void setError(int error) { |
| mCurrentError = error; |
| doStateTransition(STATE_ERROR); |
| } |
| |
| /** |
| * Transition to the {@code CONFIGURING} state, or {@code ERROR} if in an invalid state. |
| * |
| * <p> |
| * If the device was not already in the {@code CONFIGURING} state, |
| * {@link CameraDeviceStateListener#onConfiguring()} will be called. |
| * </p> |
| * |
| * @return {@code false} if an error has occurred. |
| */ |
| public synchronized boolean setConfiguring() { |
| doStateTransition(STATE_CONFIGURING); |
| return mCurrentError == NO_CAPTURE_ERROR; |
| } |
| |
| /** |
| * Transition to the {@code IDLE} state, or {@code ERROR} if in an invalid state. |
| * |
| * <p> |
| * If the device was not already in the {@code IDLE} state, |
| * {@link CameraDeviceStateListener#onIdle()} will be called. |
| * </p> |
| * |
| * @return {@code false} if an error has occurred. |
| */ |
| public synchronized boolean setIdle() { |
| doStateTransition(STATE_IDLE); |
| return mCurrentError == NO_CAPTURE_ERROR; |
| } |
| |
| /** |
| * Transition to the {@code CAPTURING} state, or {@code ERROR} if in an invalid state. |
| * |
| * <p> |
| * If the device was not already in the {@code CAPTURING} state, |
| * {@link CameraDeviceStateListener#onCaptureStarted(RequestHolder)} will be called. |
| * </p> |
| * |
| * @param request A {@link RequestHolder} containing the request for the current capture. |
| * @param timestamp The timestamp of the capture start in nanoseconds. |
| * @param captureError Report a recoverable error for a single request using a valid |
| * error code for {@code ICameraDeviceCallbacks}, or |
| * {@link #NO_CAPTURE_ERROR} |
| * @return {@code false} if an error has occurred. |
| */ |
| public synchronized boolean setCaptureStart(final RequestHolder request, long timestamp, |
| int captureError) { |
| mCurrentRequest = request; |
| doStateTransition(STATE_CAPTURING, timestamp, captureError); |
| return mCurrentError == NO_CAPTURE_ERROR; |
| } |
| |
| /** |
| * Set the result for a capture. |
| * |
| * <p> |
| * If the device was in the {@code CAPTURING} state, |
| * {@link CameraDeviceStateListener#onCaptureResult(CameraMetadataNative, RequestHolder)} will |
| * be called with the given result, otherwise this will result in the device transitioning to |
| * the {@code ERROR} state, |
| * </p> |
| * |
| * @param request The {@link RequestHolder} request that created this result. |
| * @param result The {@link CameraMetadataNative} result to set. |
| * @param captureError Report a recoverable error for a single buffer or result using a valid |
| * error code for {@code ICameraDeviceCallbacks}, or |
| * {@link #NO_CAPTURE_ERROR}. |
| * @param captureErrorArg An argument for some error captureError codes. |
| * @return {@code false} if an error has occurred. |
| */ |
| public synchronized boolean setCaptureResult(final RequestHolder request, |
| final CameraMetadataNative result, |
| final int captureError, final Object captureErrorArg) { |
| if (mCurrentState != STATE_CAPTURING) { |
| Log.e(TAG, "Cannot receive result while in state: " + mCurrentState); |
| mCurrentError = CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE; |
| doStateTransition(STATE_ERROR); |
| return mCurrentError == NO_CAPTURE_ERROR; |
| } |
| |
| if (mCurrentHandler != null && mCurrentListener != null) { |
| if (captureError != NO_CAPTURE_ERROR) { |
| mCurrentHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mCurrentListener.onError(captureError, captureErrorArg, request); |
| } |
| }); |
| } else { |
| mCurrentHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mCurrentListener.onCaptureResult(result, request); |
| } |
| }); |
| } |
| } |
| return mCurrentError == NO_CAPTURE_ERROR; |
| } |
| |
| public synchronized boolean setCaptureResult(final RequestHolder request, |
| final CameraMetadataNative result) { |
| return setCaptureResult(request, result, NO_CAPTURE_ERROR, /*errorArg*/null); |
| } |
| |
| /** |
| * Set repeating request error. |
| * |
| * <p>Repeating request has been stopped due to an error such as abandoned output surfaces.</p> |
| * |
| * @param lastFrameNumber Frame number of the last repeating request before it is stopped. |
| */ |
| public synchronized void setRepeatingRequestError(final long lastFrameNumber) { |
| mCurrentHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mCurrentListener.onRepeatingRequestError(lastFrameNumber); |
| } |
| }); |
| } |
| |
| /** |
| * Indicate that request queue (non-repeating) becomes empty. |
| * |
| * <p> Send notification that all non-repeating requests have been sent to camera device. </p> |
| */ |
| public synchronized void setRequestQueueEmpty() { |
| mCurrentHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mCurrentListener.onRequestQueueEmpty(); |
| } |
| }); |
| } |
| |
| /** |
| * Set the listener for state transition callbacks. |
| * |
| * @param handler handler on which to call the callbacks. |
| * @param listener the {@link CameraDeviceStateListener} callbacks to call. |
| */ |
| public synchronized void setCameraDeviceCallbacks(Handler handler, |
| CameraDeviceStateListener listener) { |
| mCurrentHandler = handler; |
| mCurrentListener = listener; |
| } |
| |
| private void doStateTransition(int newState) { |
| doStateTransition(newState, /*timestamp*/0, NO_CAPTURE_ERROR); |
| } |
| |
| private void doStateTransition(int newState, final long timestamp, final int error) { |
| if (newState != mCurrentState) { |
| String stateName = "UNKNOWN"; |
| if (newState >= 0 && newState < sStateNames.length) { |
| stateName = sStateNames[newState]; |
| } |
| Log.i(TAG, "Legacy camera service transitioning to state " + stateName); |
| } |
| |
| // If we transitioned into a non-IDLE/non-ERROR state then mark the device as busy |
| if(newState != STATE_ERROR && newState != STATE_IDLE) { |
| if (mCurrentState != newState && mCurrentHandler != null && |
| mCurrentListener != null) { |
| mCurrentHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mCurrentListener.onBusy(); |
| } |
| }); |
| } |
| } |
| |
| switch(newState) { |
| case STATE_ERROR: |
| if (mCurrentState != STATE_ERROR && mCurrentHandler != null && |
| mCurrentListener != null) { |
| mCurrentHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mCurrentListener.onError(mCurrentError, /*errorArg*/null, mCurrentRequest); |
| } |
| }); |
| } |
| mCurrentState = STATE_ERROR; |
| break; |
| case STATE_CONFIGURING: |
| if (mCurrentState != STATE_UNCONFIGURED && mCurrentState != STATE_IDLE) { |
| Log.e(TAG, "Cannot call configure while in state: " + mCurrentState); |
| mCurrentError = CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE; |
| doStateTransition(STATE_ERROR); |
| break; |
| } |
| if (mCurrentState != STATE_CONFIGURING && mCurrentHandler != null && |
| mCurrentListener != null) { |
| mCurrentHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mCurrentListener.onConfiguring(); |
| } |
| }); |
| } |
| mCurrentState = STATE_CONFIGURING; |
| break; |
| case STATE_IDLE: |
| if (mCurrentState == STATE_IDLE) { |
| break; |
| } |
| |
| if (mCurrentState != STATE_CONFIGURING && mCurrentState != STATE_CAPTURING) { |
| Log.e(TAG, "Cannot call idle while in state: " + mCurrentState); |
| mCurrentError = CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE; |
| doStateTransition(STATE_ERROR); |
| break; |
| } |
| |
| if (mCurrentState != STATE_IDLE && mCurrentHandler != null && |
| mCurrentListener != null) { |
| mCurrentHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mCurrentListener.onIdle(); |
| } |
| }); |
| } |
| mCurrentState = STATE_IDLE; |
| break; |
| case STATE_CAPTURING: |
| if (mCurrentState != STATE_IDLE && mCurrentState != STATE_CAPTURING) { |
| Log.e(TAG, "Cannot call capture while in state: " + mCurrentState); |
| mCurrentError = CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE; |
| doStateTransition(STATE_ERROR); |
| break; |
| } |
| |
| if (mCurrentHandler != null && mCurrentListener != null) { |
| if (error != NO_CAPTURE_ERROR) { |
| mCurrentHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mCurrentListener.onError(error, /*errorArg*/null, mCurrentRequest); |
| } |
| }); |
| } else { |
| mCurrentHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mCurrentListener.onCaptureStarted(mCurrentRequest, timestamp); |
| } |
| }); |
| } |
| } |
| mCurrentState = STATE_CAPTURING; |
| break; |
| default: |
| throw new IllegalStateException("Transition to unknown state: " + newState); |
| } |
| } |
| |
| |
| } |