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