Home | History | Annotate | Download | only in legacy
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      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
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     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.
     15  */
     16 
     17 package android.hardware.camera2.legacy;
     18 
     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;
     42 
     43 import java.util.ArrayList;
     44 import java.util.List;
     45 
     46 import static android.system.OsConstants.EACCES;
     47 import static android.system.OsConstants.ENODEV;
     48 
     49 /**
     50  * Compatibility implementation of the Camera2 API binder interface.
     51  *
     52  * <p>
     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
     56  * the Camera1 API.
     57  * </p>
     58  *
     59  * <p>
     60  * Keep up to date with ICameraDeviceUser.aidl.
     61  * </p>
     62  */
     63 @SuppressWarnings("deprecation")
     64 public class CameraDeviceUserShim implements ICameraDeviceUser {
     65     private static final String TAG = "CameraDeviceUserShim";
     66 
     67     private static final boolean DEBUG = false;
     68     private static final int OPEN_CAMERA_TIMEOUT_MS = 5000; // 5 sec (same as api1 cts timeout)
     69 
     70     private final LegacyCameraDevice mLegacyDevice;
     71 
     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;
     79 
     80 
     81     protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera,
     82             CameraCharacteristics characteristics, CameraLooper cameraInit,
     83             CameraCallbackThread cameraCallbacks) {
     84         mLegacyDevice = legacyCamera;
     85         mConfiguring = false;
     86         mSurfaces = new SparseArray<Surface>();
     87         mCameraCharacteristics = characteristics;
     88         mCameraInit = cameraInit;
     89         mCameraCallbacks = cameraCallbacks;
     90 
     91         mSurfaceIdCounter = 0;
     92     }
     93 
     94     private static int translateErrorsFromCamera1(int errorCode) {
     95         if (errorCode == -EACCES) {
     96             return ICameraService.ERROR_PERMISSION_DENIED;
     97         }
     98 
     99         return errorCode;
    100     }
    101 
    102     /**
    103      * Create a separate looper/thread for the camera to run on; open the camera.
    104      *
    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>
    108      */
    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;
    116 
    117         /**
    118          * Spin up a new thread, immediately open the camera in the background.
    119          *
    120          * <p>Use {@link #waitForOpen} to block until the camera is finished opening.</p>
    121          *
    122          * @param cameraId numeric camera Id
    123          *
    124          * @see #waitForOpen
    125          */
    126         public CameraLooper(int cameraId) {
    127             mCameraId = cameraId;
    128 
    129             mThread = new Thread(this);
    130             mThread.start();
    131         }
    132 
    133         public Camera getCamera() {
    134             return mCamera;
    135         }
    136 
    137         @Override
    138         public void run() {
    139             // Set up a looper to be used by camera.
    140             Looper.prepare();
    141 
    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);
    146             mStartDone.open();
    147             Looper.loop();  // Blocks forever until #close is called.
    148         }
    149 
    150         /**
    151          * Quit the looper safely; then join until the thread shuts down.
    152          */
    153         @Override
    154         public void close() {
    155             if (mLooper == null) {
    156                 return;
    157             }
    158 
    159             mLooper.quitSafely();
    160             try {
    161                 mThread.join();
    162             } catch (InterruptedException e) {
    163                 throw new AssertionError(e);
    164             }
    165 
    166             mLooper = null;
    167         }
    168 
    169         /**
    170          * Block until the camera opens; then return its initialization error code (if any).
    171          *
    172          * @param timeoutMs timeout in milliseconds
    173          *
    174          * @return int error code
    175          *
    176          * @throws ServiceSpecificException if the camera open times out with ({@code CAMERA_ERROR})
    177          */
    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");
    183                 try {
    184                     mCamera.release();
    185                 } catch (RuntimeException e) {
    186                     Log.e(TAG, "connectBinderShim - Failed to release camera after timeout ", e);
    187                 }
    188 
    189                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION);
    190             }
    191 
    192             return mInitErrors;
    193         }
    194     }
    195 
    196     /**
    197      * A thread to process callbacks to send back to the camera client.
    198      *
    199      * <p>This effectively emulates one-way binder semantics when in the same process as the
    200      * callee.</p>
    201      */
    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;
    209         private static final int REQUEST_QUEUE_EMPTY = 6;
    210 
    211         private final HandlerThread mHandlerThread;
    212         private Handler mHandler;
    213 
    214         private final ICameraDeviceCallbacks mCallbacks;
    215 
    216         public CameraCallbackThread(ICameraDeviceCallbacks callbacks) {
    217             mCallbacks = callbacks;
    218 
    219             mHandlerThread = new HandlerThread("LegacyCameraCallback");
    220             mHandlerThread.start();
    221         }
    222 
    223         public void close() {
    224             mHandlerThread.quitSafely();
    225         }
    226 
    227         @Override
    228         public void onDeviceError(final int errorCode, final CaptureResultExtras resultExtras) {
    229             Message msg = getHandler().obtainMessage(CAMERA_ERROR,
    230                 /*arg1*/ errorCode, /*arg2*/ 0,
    231                 /*obj*/ resultExtras);
    232             getHandler().sendMessage(msg);
    233         }
    234 
    235         @Override
    236         public void onDeviceIdle() {
    237             Message msg = getHandler().obtainMessage(CAMERA_IDLE);
    238             getHandler().sendMessage(msg);
    239         }
    240 
    241         @Override
    242         public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
    243             Message msg = getHandler().obtainMessage(CAPTURE_STARTED,
    244                     /*arg1*/ (int) (timestamp & 0xFFFFFFFFL),
    245                     /*arg2*/ (int) ( (timestamp >> 32) & 0xFFFFFFFFL),
    246                     /*obj*/ resultExtras);
    247             getHandler().sendMessage(msg);
    248         }
    249 
    250         @Override
    251         public void onResultReceived(final CameraMetadataNative result,
    252                 final CaptureResultExtras resultExtras) {
    253             Object[] resultArray = new Object[] { result, resultExtras };
    254             Message msg = getHandler().obtainMessage(RESULT_RECEIVED,
    255                     /*obj*/ resultArray);
    256             getHandler().sendMessage(msg);
    257         }
    258 
    259         @Override
    260         public void onPrepared(int streamId) {
    261             Message msg = getHandler().obtainMessage(PREPARED,
    262                     /*arg1*/ streamId, /*arg2*/ 0);
    263             getHandler().sendMessage(msg);
    264         }
    265 
    266         @Override
    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);
    272         }
    273 
    274         @Override
    275         public void onRequestQueueEmpty() {
    276             Message msg = getHandler().obtainMessage(REQUEST_QUEUE_EMPTY,
    277                     /* arg1 */ 0, /* arg2 */ 0);
    278             getHandler().sendMessage(msg);
    279         }
    280 
    281         @Override
    282         public IBinder asBinder() {
    283             // This is solely intended to be used for in-process binding.
    284             return null;
    285         }
    286 
    287         private Handler getHandler() {
    288             if (mHandler == null) {
    289                 mHandler = new CallbackHandler(mHandlerThread.getLooper());
    290             }
    291             return mHandler;
    292         }
    293 
    294         private class CallbackHandler extends Handler {
    295             public CallbackHandler(Looper l) {
    296                 super(l);
    297             }
    298 
    299             @Override
    300             public void handleMessage(Message msg) {
    301                 try {
    302                     switch (msg.what) {
    303                         case CAMERA_ERROR: {
    304                             int errorCode = msg.arg1;
    305                             CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj;
    306                             mCallbacks.onDeviceError(errorCode, resultExtras);
    307                             break;
    308                         }
    309                         case CAMERA_IDLE:
    310                             mCallbacks.onDeviceIdle();
    311                             break;
    312                         case CAPTURE_STARTED: {
    313                             long timestamp = msg.arg2 & 0xFFFFFFFFL;
    314                             timestamp = (timestamp << 32) | (msg.arg1 & 0xFFFFFFFFL);
    315                             CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj;
    316                             mCallbacks.onCaptureStarted(resultExtras, timestamp);
    317                             break;
    318                         }
    319                         case RESULT_RECEIVED: {
    320                             Object[] resultArray = (Object[]) msg.obj;
    321                             CameraMetadataNative result = (CameraMetadataNative) resultArray[0];
    322                             CaptureResultExtras resultExtras = (CaptureResultExtras) resultArray[1];
    323                             mCallbacks.onResultReceived(result, resultExtras);
    324                             break;
    325                         }
    326                         case PREPARED: {
    327                             int streamId = msg.arg1;
    328                             mCallbacks.onPrepared(streamId);
    329                             break;
    330                         }
    331                         case REPEATING_REQUEST_ERROR: {
    332                             long lastFrameNumber = msg.arg2 & 0xFFFFFFFFL;
    333                             lastFrameNumber = (lastFrameNumber << 32) | (msg.arg1 & 0xFFFFFFFFL);
    334                             mCallbacks.onRepeatingRequestError(lastFrameNumber);
    335                             break;
    336                         }
    337                         case REQUEST_QUEUE_EMPTY: {
    338                             mCallbacks.onRequestQueueEmpty();
    339                             break;
    340                         }
    341                         default:
    342                             throw new IllegalArgumentException(
    343                                 "Unknown callback message " + msg.what);
    344                     }
    345                 } catch (RemoteException e) {
    346                     throw new IllegalStateException(
    347                         "Received remote exception during camera callback " + msg.what, e);
    348                 }
    349             }
    350         }
    351     }
    352 
    353     public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks,
    354                                                          int cameraId) {
    355         if (DEBUG) {
    356             Log.d(TAG, "Opening shim Camera device");
    357         }
    358 
    359         /*
    360          * Put the camera open on a separate thread with its own looper; otherwise
    361          * if the main thread is used then the callbacks might never get delivered
    362          * (e.g. in CTS which run its own default looper only after tests)
    363          */
    364 
    365         CameraLooper init = new CameraLooper(cameraId);
    366 
    367         CameraCallbackThread threadCallbacks = new CameraCallbackThread(callbacks);
    368 
    369         // TODO: Make this async instead of blocking
    370         int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS);
    371         Camera legacyCamera = init.getCamera();
    372 
    373         // Check errors old HAL initialization
    374         LegacyExceptionUtils.throwOnServiceError(initErrors);
    375 
    376         // Disable shutter sounds (this will work unconditionally) for api2 clients
    377         legacyCamera.disableShutterSound();
    378 
    379         CameraInfo info = new CameraInfo();
    380         Camera.getCameraInfo(cameraId, info);
    381 
    382         Camera.Parameters legacyParameters = null;
    383         try {
    384             legacyParameters = legacyCamera.getParameters();
    385         } catch (RuntimeException e) {
    386             throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION,
    387                     "Unable to get initial parameters: " + e.getMessage());
    388         }
    389 
    390         CameraCharacteristics characteristics =
    391                 LegacyMetadataMapper.createCharacteristics(legacyParameters, info);
    392         LegacyCameraDevice device = new LegacyCameraDevice(
    393                 cameraId, legacyCamera, characteristics, threadCallbacks);
    394         return new CameraDeviceUserShim(cameraId, device, characteristics, init, threadCallbacks);
    395     }
    396 
    397     @Override
    398     public void disconnect() {
    399         if (DEBUG) {
    400             Log.d(TAG, "disconnect called.");
    401         }
    402 
    403         if (mLegacyDevice.isClosed()) {
    404             Log.w(TAG, "Cannot disconnect, device has already been closed.");
    405         }
    406 
    407         try {
    408             mLegacyDevice.close();
    409         } finally {
    410             mCameraInit.close();
    411             mCameraCallbacks.close();
    412         }
    413     }
    414 
    415     @Override
    416     public SubmitInfo submitRequest(CaptureRequest request, boolean streaming) {
    417         if (DEBUG) {
    418             Log.d(TAG, "submitRequest called.");
    419         }
    420         if (mLegacyDevice.isClosed()) {
    421             String err = "Cannot submit request, device has been closed.";
    422             Log.e(TAG, err);
    423             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
    424         }
    425 
    426         synchronized(mConfigureLock) {
    427             if (mConfiguring) {
    428                 String err = "Cannot submit request, configuration change in progress.";
    429                 Log.e(TAG, err);
    430                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
    431             }
    432         }
    433         return mLegacyDevice.submitRequest(request, streaming);
    434     }
    435 
    436     @Override
    437     public SubmitInfo submitRequestList(CaptureRequest[] request, boolean streaming) {
    438         if (DEBUG) {
    439             Log.d(TAG, "submitRequestList called.");
    440         }
    441         if (mLegacyDevice.isClosed()) {
    442             String err = "Cannot submit request list, device has been closed.";
    443             Log.e(TAG, err);
    444             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
    445         }
    446 
    447         synchronized(mConfigureLock) {
    448             if (mConfiguring) {
    449                 String err = "Cannot submit request, configuration change in progress.";
    450                 Log.e(TAG, err);
    451                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
    452             }
    453         }
    454         return mLegacyDevice.submitRequestList(request, streaming);
    455     }
    456 
    457     @Override
    458     public long cancelRequest(int requestId) {
    459         if (DEBUG) {
    460             Log.d(TAG, "cancelRequest called.");
    461         }
    462         if (mLegacyDevice.isClosed()) {
    463             String err = "Cannot cancel request, device has been closed.";
    464             Log.e(TAG, err);
    465             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
    466         }
    467 
    468         synchronized(mConfigureLock) {
    469             if (mConfiguring) {
    470                 String err = "Cannot cancel request, configuration change in progress.";
    471                 Log.e(TAG, err);
    472                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
    473             }
    474         }
    475         return mLegacyDevice.cancelRequest(requestId);
    476     }
    477 
    478     @Override
    479     public void beginConfigure() {
    480         if (DEBUG) {
    481             Log.d(TAG, "beginConfigure called.");
    482         }
    483         if (mLegacyDevice.isClosed()) {
    484             String err = "Cannot begin configure, device has been closed.";
    485             Log.e(TAG, err);
    486             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
    487         }
    488 
    489         synchronized(mConfigureLock) {
    490             if (mConfiguring) {
    491                 String err = "Cannot begin configure, configuration change already in progress.";
    492                 Log.e(TAG, err);
    493                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
    494             }
    495             mConfiguring = true;
    496         }
    497     }
    498 
    499     @Override
    500     public void endConfigure(int operatingMode) {
    501         if (DEBUG) {
    502             Log.d(TAG, "endConfigure called.");
    503         }
    504         if (mLegacyDevice.isClosed()) {
    505             String err = "Cannot end configure, device has been closed.";
    506             Log.e(TAG, err);
    507             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
    508         }
    509 
    510         if (operatingMode != ICameraDeviceUser.NORMAL_MODE) {
    511             String err = "LEGACY devices do not support this operating mode";
    512             Log.e(TAG, err);
    513             throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
    514         }
    515 
    516         SparseArray<Surface> surfaces = null;
    517         synchronized(mConfigureLock) {
    518             if (!mConfiguring) {
    519                 String err = "Cannot end configure, no configuration change in progress.";
    520                 Log.e(TAG, err);
    521                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
    522             }
    523             if (mSurfaces != null) {
    524                 surfaces = mSurfaces.clone();
    525             }
    526             mConfiguring = false;
    527         }
    528         mLegacyDevice.configureOutputs(surfaces);
    529     }
    530 
    531     @Override
    532     public void deleteStream(int streamId) {
    533         if (DEBUG) {
    534             Log.d(TAG, "deleteStream called.");
    535         }
    536         if (mLegacyDevice.isClosed()) {
    537             String err = "Cannot delete stream, device has been closed.";
    538             Log.e(TAG, err);
    539             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
    540         }
    541 
    542         synchronized(mConfigureLock) {
    543             if (!mConfiguring) {
    544                 String err = "Cannot delete stream, no configuration change in progress.";
    545                 Log.e(TAG, err);
    546                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
    547             }
    548             int index = mSurfaces.indexOfKey(streamId);
    549             if (index < 0) {
    550                 String err = "Cannot delete stream, stream id " + streamId + " doesn't exist.";
    551                 Log.e(TAG, err);
    552                 throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
    553             }
    554             mSurfaces.removeAt(index);
    555         }
    556     }
    557 
    558     @Override
    559     public int createStream(OutputConfiguration outputConfiguration) {
    560         if (DEBUG) {
    561             Log.d(TAG, "createStream called.");
    562         }
    563         if (mLegacyDevice.isClosed()) {
    564             String err = "Cannot create stream, device has been closed.";
    565             Log.e(TAG, err);
    566             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
    567         }
    568 
    569         synchronized(mConfigureLock) {
    570             if (!mConfiguring) {
    571                 String err = "Cannot create stream, beginConfigure hasn't been called yet.";
    572                 Log.e(TAG, err);
    573                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
    574             }
    575             if (outputConfiguration.getRotation() != OutputConfiguration.ROTATION_0) {
    576                 String err = "Cannot create stream, stream rotation is not supported.";
    577                 Log.e(TAG, err);
    578                 throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
    579             }
    580             int id = ++mSurfaceIdCounter;
    581             mSurfaces.put(id, outputConfiguration.getSurface());
    582             return id;
    583         }
    584     }
    585 
    586     @Override
    587     public void finalizeOutputConfigurations(int steamId, OutputConfiguration config) {
    588         String err = "Finalizing output configuration is not supported on legacy devices";
    589         Log.e(TAG, err);
    590         throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
    591     }
    592 
    593     @Override
    594     public int createInputStream(int width, int height, int format) {
    595         String err = "Creating input stream is not supported on legacy devices";
    596         Log.e(TAG, err);
    597         throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
    598     }
    599 
    600     @Override
    601     public Surface getInputSurface() {
    602         String err = "Getting input surface is not supported on legacy devices";
    603         Log.e(TAG, err);
    604         throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
    605     }
    606 
    607     @Override
    608     public CameraMetadataNative createDefaultRequest(int templateId) {
    609         if (DEBUG) {
    610             Log.d(TAG, "createDefaultRequest called.");
    611         }
    612         if (mLegacyDevice.isClosed()) {
    613             String err = "Cannot create default request, device has been closed.";
    614             Log.e(TAG, err);
    615             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
    616         }
    617 
    618         CameraMetadataNative template;
    619         try {
    620             template =
    621                     LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId);
    622         } catch (IllegalArgumentException e) {
    623             String err = "createDefaultRequest - invalid templateId specified";
    624             Log.e(TAG, err);
    625             throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
    626         }
    627 
    628         return template;
    629     }
    630 
    631     @Override
    632     public CameraMetadataNative getCameraInfo() {
    633         if (DEBUG) {
    634             Log.d(TAG, "getCameraInfo called.");
    635         }
    636         // TODO: implement getCameraInfo.
    637         Log.e(TAG, "getCameraInfo unimplemented.");
    638         return null;
    639     }
    640 
    641     @Override
    642     public void waitUntilIdle() throws RemoteException {
    643         if (DEBUG) {
    644             Log.d(TAG, "waitUntilIdle called.");
    645         }
    646         if (mLegacyDevice.isClosed()) {
    647             String err = "Cannot wait until idle, device has been closed.";
    648             Log.e(TAG, err);
    649             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
    650         }
    651 
    652         synchronized(mConfigureLock) {
    653             if (mConfiguring) {
    654                 String err = "Cannot wait until idle, configuration change in progress.";
    655                 Log.e(TAG, err);
    656                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
    657             }
    658         }
    659         mLegacyDevice.waitUntilIdle();
    660     }
    661 
    662     @Override
    663     public long flush() {
    664         if (DEBUG) {
    665             Log.d(TAG, "flush called.");
    666         }
    667         if (mLegacyDevice.isClosed()) {
    668             String err = "Cannot flush, device has been closed.";
    669             Log.e(TAG, err);
    670             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
    671         }
    672 
    673         synchronized(mConfigureLock) {
    674             if (mConfiguring) {
    675                 String err = "Cannot flush, configuration change in progress.";
    676                 Log.e(TAG, err);
    677                 throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
    678             }
    679         }
    680         return mLegacyDevice.flush();
    681     }
    682 
    683     public void prepare(int streamId) {
    684         if (DEBUG) {
    685             Log.d(TAG, "prepare called.");
    686         }
    687         if (mLegacyDevice.isClosed()) {
    688             String err = "Cannot prepare stream, device has been closed.";
    689             Log.e(TAG, err);
    690             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
    691         }
    692 
    693         // LEGACY doesn't support actual prepare, just signal success right away
    694         mCameraCallbacks.onPrepared(streamId);
    695     }
    696 
    697     public void prepare2(int maxCount, int streamId) {
    698         // We don't support this in LEGACY mode.
    699         prepare(streamId);
    700     }
    701 
    702     public void tearDown(int streamId) {
    703         if (DEBUG) {
    704             Log.d(TAG, "tearDown called.");
    705         }
    706         if (mLegacyDevice.isClosed()) {
    707             String err = "Cannot tear down stream, device has been closed.";
    708             Log.e(TAG, err);
    709             throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
    710         }
    711 
    712         // LEGACY doesn't support actual teardown, so just a no-op
    713     }
    714 
    715     @Override
    716     public IBinder asBinder() {
    717         // This is solely intended to be used for in-process binding.
    718         return null;
    719     }
    720 }
    721