/* * 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.ICameraService; import android.hardware.Camera; import android.hardware.Camera.CameraInfo; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.CaptureResultExtras; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.utils.SubmitInfo; import android.os.ConditionVariable; import android.os.IBinder; import android.os.Looper; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; import android.util.SparseArray; import android.view.Surface; import java.util.ArrayList; import java.util.List; import static android.system.OsConstants.EACCES; import static android.system.OsConstants.ENODEV; /** * Compatibility implementation of the Camera2 API binder interface. * *

* This is intended to be called from the same process as client * {@link android.hardware.camera2.CameraDevice}, and wraps a * {@link android.hardware.camera2.legacy.LegacyCameraDevice} that emulates Camera2 service using * the Camera1 API. *

* *

* Keep up to date with ICameraDeviceUser.aidl. *

*/ @SuppressWarnings("deprecation") public class CameraDeviceUserShim implements ICameraDeviceUser { private static final String TAG = "CameraDeviceUserShim"; private static final boolean DEBUG = false; private static final int OPEN_CAMERA_TIMEOUT_MS = 5000; // 5 sec (same as api1 cts timeout) private final LegacyCameraDevice mLegacyDevice; private final Object mConfigureLock = new Object(); private int mSurfaceIdCounter; private boolean mConfiguring; private final SparseArray mSurfaces; private final CameraCharacteristics mCameraCharacteristics; private final CameraLooper mCameraInit; private final CameraCallbackThread mCameraCallbacks; protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera, CameraCharacteristics characteristics, CameraLooper cameraInit, CameraCallbackThread cameraCallbacks) { mLegacyDevice = legacyCamera; mConfiguring = false; mSurfaces = new SparseArray(); mCameraCharacteristics = characteristics; mCameraInit = cameraInit; mCameraCallbacks = cameraCallbacks; mSurfaceIdCounter = 0; } private static int translateErrorsFromCamera1(int errorCode) { if (errorCode == -EACCES) { return ICameraService.ERROR_PERMISSION_DENIED; } return errorCode; } /** * Create a separate looper/thread for the camera to run on; open the camera. * *

Since the camera automatically latches on to the current thread's looper, * it's important that we have our own thread with our own looper to guarantee * that the camera callbacks get correctly posted to our own thread.

*/ private static class CameraLooper implements Runnable, AutoCloseable { private final int mCameraId; private Looper mLooper; private volatile int mInitErrors; private final Camera mCamera = Camera.openUninitialized(); private final ConditionVariable mStartDone = new ConditionVariable(); private final Thread mThread; /** * Spin up a new thread, immediately open the camera in the background. * *

Use {@link #waitForOpen} to block until the camera is finished opening.

* * @param cameraId numeric camera Id * * @see #waitForOpen */ public CameraLooper(int cameraId) { mCameraId = cameraId; mThread = new Thread(this); mThread.start(); } public Camera getCamera() { return mCamera; } @Override public void run() { // Set up a looper to be used by camera. Looper.prepare(); // Save the looper so that we can terminate this thread // after we are done with it. mLooper = Looper.myLooper(); mInitErrors = mCamera.cameraInitUnspecified(mCameraId); mStartDone.open(); Looper.loop(); // Blocks forever until #close is called. } /** * Quit the looper safely; then join until the thread shuts down. */ @Override public void close() { if (mLooper == null) { return; } mLooper.quitSafely(); try { mThread.join(); } catch (InterruptedException e) { throw new AssertionError(e); } mLooper = null; } /** * Block until the camera opens; then return its initialization error code (if any). * * @param timeoutMs timeout in milliseconds * * @return int error code * * @throws ServiceSpecificException if the camera open times out with ({@code CAMERA_ERROR}) */ public int waitForOpen(int timeoutMs) { // Block until the camera is open asynchronously if (!mStartDone.block(timeoutMs)) { Log.e(TAG, "waitForOpen - Camera failed to open after timeout of " + OPEN_CAMERA_TIMEOUT_MS + " ms"); try { mCamera.release(); } catch (RuntimeException e) { Log.e(TAG, "connectBinderShim - Failed to release camera after timeout ", e); } throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION); } return mInitErrors; } } /** * A thread to process callbacks to send back to the camera client. * *

This effectively emulates one-way binder semantics when in the same process as the * callee.

*/ private static class CameraCallbackThread implements ICameraDeviceCallbacks { private static final int CAMERA_ERROR = 0; private static final int CAMERA_IDLE = 1; private static final int CAPTURE_STARTED = 2; private static final int RESULT_RECEIVED = 3; private static final int PREPARED = 4; private static final int REPEATING_REQUEST_ERROR = 5; private final HandlerThread mHandlerThread; private Handler mHandler; private final ICameraDeviceCallbacks mCallbacks; public CameraCallbackThread(ICameraDeviceCallbacks callbacks) { mCallbacks = callbacks; mHandlerThread = new HandlerThread("LegacyCameraCallback"); mHandlerThread.start(); } public void close() { mHandlerThread.quitSafely(); } @Override public void onDeviceError(final int errorCode, final CaptureResultExtras resultExtras) { Message msg = getHandler().obtainMessage(CAMERA_ERROR, /*arg1*/ errorCode, /*arg2*/ 0, /*obj*/ resultExtras); getHandler().sendMessage(msg); } @Override public void onDeviceIdle() { Message msg = getHandler().obtainMessage(CAMERA_IDLE); getHandler().sendMessage(msg); } @Override public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) { Message msg = getHandler().obtainMessage(CAPTURE_STARTED, /*arg1*/ (int) (timestamp & 0xFFFFFFFFL), /*arg2*/ (int) ( (timestamp >> 32) & 0xFFFFFFFFL), /*obj*/ resultExtras); getHandler().sendMessage(msg); } @Override public void onResultReceived(final CameraMetadataNative result, final CaptureResultExtras resultExtras) { Object[] resultArray = new Object[] { result, resultExtras }; Message msg = getHandler().obtainMessage(RESULT_RECEIVED, /*obj*/ resultArray); getHandler().sendMessage(msg); } @Override public void onPrepared(int streamId) { Message msg = getHandler().obtainMessage(PREPARED, /*arg1*/ streamId, /*arg2*/ 0); getHandler().sendMessage(msg); } @Override public void onRepeatingRequestError(long lastFrameNumber) { Message msg = getHandler().obtainMessage(REPEATING_REQUEST_ERROR, /*arg1*/ (int) (lastFrameNumber & 0xFFFFFFFFL), /*arg2*/ (int) ( (lastFrameNumber >> 32) & 0xFFFFFFFFL)); getHandler().sendMessage(msg); } @Override public IBinder asBinder() { // This is solely intended to be used for in-process binding. return null; } private Handler getHandler() { if (mHandler == null) { mHandler = new CallbackHandler(mHandlerThread.getLooper()); } return mHandler; } private class CallbackHandler extends Handler { public CallbackHandler(Looper l) { super(l); } @Override public void handleMessage(Message msg) { try { switch (msg.what) { case CAMERA_ERROR: { int errorCode = msg.arg1; CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj; mCallbacks.onDeviceError(errorCode, resultExtras); break; } case CAMERA_IDLE: mCallbacks.onDeviceIdle(); break; case CAPTURE_STARTED: { long timestamp = msg.arg2 & 0xFFFFFFFFL; timestamp = (timestamp << 32) | (msg.arg1 & 0xFFFFFFFFL); CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj; mCallbacks.onCaptureStarted(resultExtras, timestamp); break; } case RESULT_RECEIVED: { Object[] resultArray = (Object[]) msg.obj; CameraMetadataNative result = (CameraMetadataNative) resultArray[0]; CaptureResultExtras resultExtras = (CaptureResultExtras) resultArray[1]; mCallbacks.onResultReceived(result, resultExtras); break; } case PREPARED: { int streamId = msg.arg1; mCallbacks.onPrepared(streamId); break; } case REPEATING_REQUEST_ERROR: { long lastFrameNumber = msg.arg2 & 0xFFFFFFFFL; lastFrameNumber = (lastFrameNumber << 32) | (msg.arg1 & 0xFFFFFFFFL); mCallbacks.onRepeatingRequestError(lastFrameNumber); break; } default: throw new IllegalArgumentException( "Unknown callback message " + msg.what); } } catch (RemoteException e) { throw new IllegalStateException( "Received remote exception during camera callback " + msg.what, e); } } } } public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks, int cameraId) { if (DEBUG) { Log.d(TAG, "Opening shim Camera device"); } /* * Put the camera open on a separate thread with its own looper; otherwise * if the main thread is used then the callbacks might never get delivered * (e.g. in CTS which run its own default looper only after tests) */ CameraLooper init = new CameraLooper(cameraId); CameraCallbackThread threadCallbacks = new CameraCallbackThread(callbacks); // TODO: Make this async instead of blocking int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS); Camera legacyCamera = init.getCamera(); // Check errors old HAL initialization LegacyExceptionUtils.throwOnServiceError(initErrors); // Disable shutter sounds (this will work unconditionally) for api2 clients legacyCamera.disableShutterSound(); CameraInfo info = new CameraInfo(); Camera.getCameraInfo(cameraId, info); Camera.Parameters legacyParameters = null; try { legacyParameters = legacyCamera.getParameters(); } catch (RuntimeException e) { throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, "Unable to get initial parameters: " + e.getMessage()); } CameraCharacteristics characteristics = LegacyMetadataMapper.createCharacteristics(legacyParameters, info); LegacyCameraDevice device = new LegacyCameraDevice( cameraId, legacyCamera, characteristics, threadCallbacks); return new CameraDeviceUserShim(cameraId, device, characteristics, init, threadCallbacks); } @Override public void disconnect() { if (DEBUG) { Log.d(TAG, "disconnect called."); } if (mLegacyDevice.isClosed()) { Log.w(TAG, "Cannot disconnect, device has already been closed."); } try { mLegacyDevice.close(); } finally { mCameraInit.close(); mCameraCallbacks.close(); } } @Override public SubmitInfo submitRequest(CaptureRequest request, boolean streaming) { if (DEBUG) { Log.d(TAG, "submitRequest called."); } if (mLegacyDevice.isClosed()) { String err = "Cannot submit request, device has been closed."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); } synchronized(mConfigureLock) { if (mConfiguring) { String err = "Cannot submit request, configuration change in progress."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); } } return mLegacyDevice.submitRequest(request, streaming); } @Override public SubmitInfo submitRequestList(CaptureRequest[] request, boolean streaming) { if (DEBUG) { Log.d(TAG, "submitRequestList called."); } if (mLegacyDevice.isClosed()) { String err = "Cannot submit request list, device has been closed."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); } synchronized(mConfigureLock) { if (mConfiguring) { String err = "Cannot submit request, configuration change in progress."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); } } return mLegacyDevice.submitRequestList(request, streaming); } @Override public long cancelRequest(int requestId) { if (DEBUG) { Log.d(TAG, "cancelRequest called."); } if (mLegacyDevice.isClosed()) { String err = "Cannot cancel request, device has been closed."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); } synchronized(mConfigureLock) { if (mConfiguring) { String err = "Cannot cancel request, configuration change in progress."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); } } return mLegacyDevice.cancelRequest(requestId); } @Override public void beginConfigure() { if (DEBUG) { Log.d(TAG, "beginConfigure called."); } if (mLegacyDevice.isClosed()) { String err = "Cannot begin configure, device has been closed."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); } synchronized(mConfigureLock) { if (mConfiguring) { String err = "Cannot begin configure, configuration change already in progress."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); } mConfiguring = true; } } @Override public void endConfigure(boolean isConstrainedHighSpeed) { if (DEBUG) { Log.d(TAG, "endConfigure called."); } if (mLegacyDevice.isClosed()) { String err = "Cannot end configure, device has been closed."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); } SparseArray surfaces = null; synchronized(mConfigureLock) { if (!mConfiguring) { String err = "Cannot end configure, no configuration change in progress."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); } if (mSurfaces != null) { surfaces = mSurfaces.clone(); } mConfiguring = false; } mLegacyDevice.configureOutputs(surfaces); } @Override public void deleteStream(int streamId) { if (DEBUG) { Log.d(TAG, "deleteStream called."); } if (mLegacyDevice.isClosed()) { String err = "Cannot delete stream, device has been closed."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); } synchronized(mConfigureLock) { if (!mConfiguring) { String err = "Cannot delete stream, no configuration change in progress."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); } int index = mSurfaces.indexOfKey(streamId); if (index < 0) { String err = "Cannot delete stream, stream id " + streamId + " doesn't exist."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err); } mSurfaces.removeAt(index); } } @Override public int createStream(OutputConfiguration outputConfiguration) { if (DEBUG) { Log.d(TAG, "createStream called."); } if (mLegacyDevice.isClosed()) { String err = "Cannot create stream, device has been closed."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); } synchronized(mConfigureLock) { if (!mConfiguring) { String err = "Cannot create stream, beginConfigure hasn't been called yet."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); } if (outputConfiguration.getRotation() != OutputConfiguration.ROTATION_0) { String err = "Cannot create stream, stream rotation is not supported."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err); } int id = ++mSurfaceIdCounter; mSurfaces.put(id, outputConfiguration.getSurface()); return id; } } @Override public int createInputStream(int width, int height, int format) { String err = "Creating input stream is not supported on legacy devices"; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); } @Override public Surface getInputSurface() { String err = "Getting input surface is not supported on legacy devices"; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); } @Override public CameraMetadataNative createDefaultRequest(int templateId) { if (DEBUG) { Log.d(TAG, "createDefaultRequest called."); } if (mLegacyDevice.isClosed()) { String err = "Cannot create default request, device has been closed."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); } CameraMetadataNative template; try { template = LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId); } catch (IllegalArgumentException e) { String err = "createDefaultRequest - invalid templateId specified"; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err); } return template; } @Override public CameraMetadataNative getCameraInfo() { if (DEBUG) { Log.d(TAG, "getCameraInfo called."); } // TODO: implement getCameraInfo. Log.e(TAG, "getCameraInfo unimplemented."); return null; } @Override public void waitUntilIdle() throws RemoteException { if (DEBUG) { Log.d(TAG, "waitUntilIdle called."); } if (mLegacyDevice.isClosed()) { String err = "Cannot wait until idle, device has been closed."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); } synchronized(mConfigureLock) { if (mConfiguring) { String err = "Cannot wait until idle, configuration change in progress."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); } } mLegacyDevice.waitUntilIdle(); } @Override public long flush() { if (DEBUG) { Log.d(TAG, "flush called."); } if (mLegacyDevice.isClosed()) { String err = "Cannot flush, device has been closed."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); } synchronized(mConfigureLock) { if (mConfiguring) { String err = "Cannot flush, configuration change in progress."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err); } } return mLegacyDevice.flush(); } public void prepare(int streamId) { if (DEBUG) { Log.d(TAG, "prepare called."); } if (mLegacyDevice.isClosed()) { String err = "Cannot prepare stream, device has been closed."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); } // LEGACY doesn't support actual prepare, just signal success right away mCameraCallbacks.onPrepared(streamId); } public void prepare2(int maxCount, int streamId) { // We don't support this in LEGACY mode. prepare(streamId); } public void tearDown(int streamId) { if (DEBUG) { Log.d(TAG, "tearDown called."); } if (mLegacyDevice.isClosed()) { String err = "Cannot tear down stream, device has been closed."; Log.e(TAG, err); throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err); } // LEGACY doesn't support actual teardown, so just a no-op } @Override public IBinder asBinder() { // This is solely intended to be used for in-process binding. return null; } }