OSDN Git Service

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