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