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