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