2 * Copyright (C) 2014 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package android.hardware.camera2.legacy;
19 import android.hardware.ICameraService;
20 import android.hardware.Camera;
21 import android.hardware.Camera.CameraInfo;
22 import android.hardware.camera2.CameraAccessException;
23 import android.hardware.camera2.CameraCharacteristics;
24 import android.hardware.camera2.CaptureRequest;
25 import android.hardware.camera2.ICameraDeviceCallbacks;
26 import android.hardware.camera2.ICameraDeviceUser;
27 import android.hardware.camera2.impl.CameraMetadataNative;
28 import android.hardware.camera2.impl.CaptureResultExtras;
29 import android.hardware.camera2.params.OutputConfiguration;
30 import android.hardware.camera2.utils.SubmitInfo;
31 import android.os.ConditionVariable;
32 import android.os.IBinder;
33 import android.os.Looper;
34 import android.os.Handler;
35 import android.os.HandlerThread;
36 import android.os.Message;
37 import android.os.RemoteException;
38 import android.os.ServiceSpecificException;
39 import android.util.Log;
40 import android.util.SparseArray;
41 import android.view.Surface;
43 import java.util.ArrayList;
44 import java.util.List;
46 import static android.system.OsConstants.EACCES;
47 import static android.system.OsConstants.ENODEV;
50 * Compatibility implementation of the Camera2 API binder interface.
53 * This is intended to be called from the same process as client
54 * {@link android.hardware.camera2.CameraDevice}, and wraps a
55 * {@link android.hardware.camera2.legacy.LegacyCameraDevice} that emulates Camera2 service using
60 * Keep up to date with ICameraDeviceUser.aidl.
63 @SuppressWarnings("deprecation")
64 public class CameraDeviceUserShim implements ICameraDeviceUser {
65 private static final String TAG = "CameraDeviceUserShim";
67 private static final boolean DEBUG = false;
68 private static final int OPEN_CAMERA_TIMEOUT_MS = 5000; // 5 sec (same as api1 cts timeout)
70 private final LegacyCameraDevice mLegacyDevice;
72 private final Object mConfigureLock = new Object();
73 private int mSurfaceIdCounter;
74 private boolean mConfiguring;
75 private final SparseArray<Surface> mSurfaces;
76 private final CameraCharacteristics mCameraCharacteristics;
77 private final CameraLooper mCameraInit;
78 private final CameraCallbackThread mCameraCallbacks;
81 protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera,
82 CameraCharacteristics characteristics, CameraLooper cameraInit,
83 CameraCallbackThread cameraCallbacks) {
84 mLegacyDevice = legacyCamera;
86 mSurfaces = new SparseArray<Surface>();
87 mCameraCharacteristics = characteristics;
88 mCameraInit = cameraInit;
89 mCameraCallbacks = cameraCallbacks;
91 mSurfaceIdCounter = 0;
94 private static int translateErrorsFromCamera1(int errorCode) {
95 if (errorCode == -EACCES) {
96 return ICameraService.ERROR_PERMISSION_DENIED;
103 * Create a separate looper/thread for the camera to run on; open the camera.
105 * <p>Since the camera automatically latches on to the current thread's looper,
106 * it's important that we have our own thread with our own looper to guarantee
107 * that the camera callbacks get correctly posted to our own thread.</p>
109 private static class CameraLooper implements Runnable, AutoCloseable {
110 private final int mCameraId;
111 private Looper mLooper;
112 private volatile int mInitErrors;
113 private final Camera mCamera = Camera.openUninitialized();
114 private final ConditionVariable mStartDone = new ConditionVariable();
115 private final Thread mThread;
118 * Spin up a new thread, immediately open the camera in the background.
120 * <p>Use {@link #waitForOpen} to block until the camera is finished opening.</p>
122 * @param cameraId numeric camera Id
126 public CameraLooper(int cameraId) {
127 mCameraId = cameraId;
129 mThread = new Thread(this);
133 public Camera getCamera() {
139 // Set up a looper to be used by camera.
142 // Save the looper so that we can terminate this thread
143 // after we are done with it.
144 mLooper = Looper.myLooper();
145 mInitErrors = mCamera.cameraInitUnspecified(mCameraId);
147 Looper.loop(); // Blocks forever until #close is called.
151 * Quit the looper safely; then join until the thread shuts down.
154 public void close() {
155 if (mLooper == null) {
159 mLooper.quitSafely();
162 } catch (InterruptedException e) {
163 throw new AssertionError(e);
170 * Block until the camera opens; then return its initialization error code (if any).
172 * @param timeoutMs timeout in milliseconds
174 * @return int error code
176 * @throws ServiceSpecificException if the camera open times out with ({@code CAMERA_ERROR})
178 public int waitForOpen(int timeoutMs) {
179 // Block until the camera is open asynchronously
180 if (!mStartDone.block(timeoutMs)) {
181 Log.e(TAG, "waitForOpen - Camera failed to open after timeout of "
182 + OPEN_CAMERA_TIMEOUT_MS + " ms");
185 } catch (RuntimeException e) {
186 Log.e(TAG, "connectBinderShim - Failed to release camera after timeout ", e);
189 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION);
197 * A thread to process callbacks to send back to the camera client.
199 * <p>This effectively emulates one-way binder semantics when in the same process as the
202 private static class CameraCallbackThread implements ICameraDeviceCallbacks {
203 private static final int CAMERA_ERROR = 0;
204 private static final int CAMERA_IDLE = 1;
205 private static final int CAPTURE_STARTED = 2;
206 private static final int RESULT_RECEIVED = 3;
207 private static final int PREPARED = 4;
208 private static final int REPEATING_REQUEST_ERROR = 5;
210 private final HandlerThread mHandlerThread;
211 private Handler mHandler;
213 private final ICameraDeviceCallbacks mCallbacks;
215 public CameraCallbackThread(ICameraDeviceCallbacks callbacks) {
216 mCallbacks = callbacks;
218 mHandlerThread = new HandlerThread("LegacyCameraCallback");
219 mHandlerThread.start();
222 public void close() {
223 mHandlerThread.quitSafely();
227 public void onDeviceError(final int errorCode, final CaptureResultExtras resultExtras) {
228 Message msg = getHandler().obtainMessage(CAMERA_ERROR,
229 /*arg1*/ errorCode, /*arg2*/ 0,
230 /*obj*/ resultExtras);
231 getHandler().sendMessage(msg);
235 public void onDeviceIdle() {
236 Message msg = getHandler().obtainMessage(CAMERA_IDLE);
237 getHandler().sendMessage(msg);
241 public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
242 Message msg = getHandler().obtainMessage(CAPTURE_STARTED,
243 /*arg1*/ (int) (timestamp & 0xFFFFFFFFL),
244 /*arg2*/ (int) ( (timestamp >> 32) & 0xFFFFFFFFL),
245 /*obj*/ resultExtras);
246 getHandler().sendMessage(msg);
250 public void onResultReceived(final CameraMetadataNative result,
251 final CaptureResultExtras resultExtras) {
252 Object[] resultArray = new Object[] { result, resultExtras };
253 Message msg = getHandler().obtainMessage(RESULT_RECEIVED,
254 /*obj*/ resultArray);
255 getHandler().sendMessage(msg);
259 public void onPrepared(int streamId) {
260 Message msg = getHandler().obtainMessage(PREPARED,
261 /*arg1*/ streamId, /*arg2*/ 0);
262 getHandler().sendMessage(msg);
267 public void onRepeatingRequestError(long lastFrameNumber) {
268 Message msg = getHandler().obtainMessage(REPEATING_REQUEST_ERROR,
269 /*arg1*/ (int) (lastFrameNumber & 0xFFFFFFFFL),
270 /*arg2*/ (int) ( (lastFrameNumber >> 32) & 0xFFFFFFFFL));
271 getHandler().sendMessage(msg);
275 public IBinder asBinder() {
276 // This is solely intended to be used for in-process binding.
280 private Handler getHandler() {
281 if (mHandler == null) {
282 mHandler = new CallbackHandler(mHandlerThread.getLooper());
287 private class CallbackHandler extends Handler {
288 public CallbackHandler(Looper l) {
293 public void handleMessage(Message msg) {
297 int errorCode = msg.arg1;
298 CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj;
299 mCallbacks.onDeviceError(errorCode, resultExtras);
303 mCallbacks.onDeviceIdle();
305 case CAPTURE_STARTED: {
306 long timestamp = msg.arg2 & 0xFFFFFFFFL;
307 timestamp = (timestamp << 32) | (msg.arg1 & 0xFFFFFFFFL);
308 CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj;
309 mCallbacks.onCaptureStarted(resultExtras, timestamp);
312 case RESULT_RECEIVED: {
313 Object[] resultArray = (Object[]) msg.obj;
314 CameraMetadataNative result = (CameraMetadataNative) resultArray[0];
315 CaptureResultExtras resultExtras = (CaptureResultExtras) resultArray[1];
316 mCallbacks.onResultReceived(result, resultExtras);
320 int streamId = msg.arg1;
321 mCallbacks.onPrepared(streamId);
324 case REPEATING_REQUEST_ERROR: {
325 long lastFrameNumber = msg.arg2 & 0xFFFFFFFFL;
326 lastFrameNumber = (lastFrameNumber << 32) | (msg.arg1 & 0xFFFFFFFFL);
327 mCallbacks.onRepeatingRequestError(lastFrameNumber);
331 throw new IllegalArgumentException(
332 "Unknown callback message " + msg.what);
334 } catch (RemoteException e) {
335 throw new IllegalStateException(
336 "Received remote exception during camera callback " + msg.what, e);
342 public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks,
345 Log.d(TAG, "Opening shim Camera device");
349 * Put the camera open on a separate thread with its own looper; otherwise
350 * if the main thread is used then the callbacks might never get delivered
351 * (e.g. in CTS which run its own default looper only after tests)
354 CameraLooper init = new CameraLooper(cameraId);
356 CameraCallbackThread threadCallbacks = new CameraCallbackThread(callbacks);
358 // TODO: Make this async instead of blocking
359 int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS);
360 Camera legacyCamera = init.getCamera();
362 // Check errors old HAL initialization
363 LegacyExceptionUtils.throwOnServiceError(initErrors);
365 // Disable shutter sounds (this will work unconditionally) for api2 clients
366 legacyCamera.disableShutterSound();
368 CameraInfo info = new CameraInfo();
369 Camera.getCameraInfo(cameraId, info);
371 Camera.Parameters legacyParameters = null;
373 legacyParameters = legacyCamera.getParameters();
374 } catch (RuntimeException e) {
375 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION,
376 "Unable to get initial parameters: " + e.getMessage());
379 CameraCharacteristics characteristics =
380 LegacyMetadataMapper.createCharacteristics(legacyParameters, info);
381 LegacyCameraDevice device = new LegacyCameraDevice(
382 cameraId, legacyCamera, characteristics, threadCallbacks);
383 return new CameraDeviceUserShim(cameraId, device, characteristics, init, threadCallbacks);
387 public void disconnect() {
389 Log.d(TAG, "disconnect called.");
392 if (mLegacyDevice.isClosed()) {
393 Log.w(TAG, "Cannot disconnect, device has already been closed.");
397 mLegacyDevice.close();
400 mCameraCallbacks.close();
405 public SubmitInfo submitRequest(CaptureRequest request, boolean streaming) {
407 Log.d(TAG, "submitRequest called.");
409 if (mLegacyDevice.isClosed()) {
410 String err = "Cannot submit request, device has been closed.";
412 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
415 synchronized(mConfigureLock) {
417 String err = "Cannot submit request, configuration change in progress.";
419 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
422 return mLegacyDevice.submitRequest(request, streaming);
426 public SubmitInfo submitRequestList(CaptureRequest[] request, boolean streaming) {
428 Log.d(TAG, "submitRequestList called.");
430 if (mLegacyDevice.isClosed()) {
431 String err = "Cannot submit request list, device has been closed.";
433 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
436 synchronized(mConfigureLock) {
438 String err = "Cannot submit request, configuration change in progress.";
440 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
443 return mLegacyDevice.submitRequestList(request, streaming);
447 public long cancelRequest(int requestId) {
449 Log.d(TAG, "cancelRequest called.");
451 if (mLegacyDevice.isClosed()) {
452 String err = "Cannot cancel request, device has been closed.";
454 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
457 synchronized(mConfigureLock) {
459 String err = "Cannot cancel request, configuration change in progress.";
461 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
464 return mLegacyDevice.cancelRequest(requestId);
468 public void beginConfigure() {
470 Log.d(TAG, "beginConfigure called.");
472 if (mLegacyDevice.isClosed()) {
473 String err = "Cannot begin configure, device has been closed.";
475 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
478 synchronized(mConfigureLock) {
480 String err = "Cannot begin configure, configuration change already in progress.";
482 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
489 public void endConfigure(boolean isConstrainedHighSpeed) {
491 Log.d(TAG, "endConfigure called.");
493 if (mLegacyDevice.isClosed()) {
494 String err = "Cannot end configure, device has been closed.";
496 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
499 SparseArray<Surface> surfaces = null;
500 synchronized(mConfigureLock) {
502 String err = "Cannot end configure, no configuration change in progress.";
504 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
506 if (mSurfaces != null) {
507 surfaces = mSurfaces.clone();
509 mConfiguring = false;
511 mLegacyDevice.configureOutputs(surfaces);
515 public void deleteStream(int streamId) {
517 Log.d(TAG, "deleteStream called.");
519 if (mLegacyDevice.isClosed()) {
520 String err = "Cannot delete stream, device has been closed.";
522 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
525 synchronized(mConfigureLock) {
527 String err = "Cannot delete stream, no configuration change in progress.";
529 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
531 int index = mSurfaces.indexOfKey(streamId);
533 String err = "Cannot delete stream, stream id " + streamId + " doesn't exist.";
535 throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
537 mSurfaces.removeAt(index);
542 public int createStream(OutputConfiguration outputConfiguration) {
544 Log.d(TAG, "createStream called.");
546 if (mLegacyDevice.isClosed()) {
547 String err = "Cannot create stream, device has been closed.";
549 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
552 synchronized(mConfigureLock) {
554 String err = "Cannot create stream, beginConfigure hasn't been called yet.";
556 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
558 if (outputConfiguration.getRotation() != OutputConfiguration.ROTATION_0) {
559 String err = "Cannot create stream, stream rotation is not supported.";
561 throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
563 int id = ++mSurfaceIdCounter;
564 mSurfaces.put(id, outputConfiguration.getSurface());
570 public int createInputStream(int width, int height, int format) {
571 String err = "Creating input stream is not supported on legacy devices";
573 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
577 public Surface getInputSurface() {
578 String err = "Getting input surface is not supported on legacy devices";
580 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
584 public CameraMetadataNative createDefaultRequest(int templateId) {
586 Log.d(TAG, "createDefaultRequest called.");
588 if (mLegacyDevice.isClosed()) {
589 String err = "Cannot create default request, device has been closed.";
591 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
594 CameraMetadataNative template;
597 LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId);
598 } catch (IllegalArgumentException e) {
599 String err = "createDefaultRequest - invalid templateId specified";
601 throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
608 public CameraMetadataNative getCameraInfo() {
610 Log.d(TAG, "getCameraInfo called.");
612 // TODO: implement getCameraInfo.
613 Log.e(TAG, "getCameraInfo unimplemented.");
618 public void waitUntilIdle() throws RemoteException {
620 Log.d(TAG, "waitUntilIdle called.");
622 if (mLegacyDevice.isClosed()) {
623 String err = "Cannot wait until idle, device has been closed.";
625 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
628 synchronized(mConfigureLock) {
630 String err = "Cannot wait until idle, configuration change in progress.";
632 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
635 mLegacyDevice.waitUntilIdle();
639 public long flush() {
641 Log.d(TAG, "flush called.");
643 if (mLegacyDevice.isClosed()) {
644 String err = "Cannot flush, device has been closed.";
646 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
649 synchronized(mConfigureLock) {
651 String err = "Cannot flush, configuration change in progress.";
653 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
656 return mLegacyDevice.flush();
659 public void prepare(int streamId) {
661 Log.d(TAG, "prepare called.");
663 if (mLegacyDevice.isClosed()) {
664 String err = "Cannot prepare stream, device has been closed.";
666 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
669 // LEGACY doesn't support actual prepare, just signal success right away
670 mCameraCallbacks.onPrepared(streamId);
673 public void prepare2(int maxCount, int streamId) {
674 // We don't support this in LEGACY mode.
678 public void tearDown(int streamId) {
680 Log.d(TAG, "tearDown called.");
682 if (mLegacyDevice.isClosed()) {
683 String err = "Cannot tear down stream, device has been closed.";
685 throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
688 // LEGACY doesn't support actual teardown, so just a no-op
692 public IBinder asBinder() {
693 // This is solely intended to be used for in-process binding.