CameraDeviceUserShim.java revision 385f9e2146d2600ae9fd20053aab8ee5abcac9a6
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
209        private final HandlerThread mHandlerThread;
210        private Handler mHandler;
211
212        private final ICameraDeviceCallbacks mCallbacks;
213
214        public CameraCallbackThread(ICameraDeviceCallbacks callbacks) {
215            mCallbacks = callbacks;
216
217            mHandlerThread = new HandlerThread("LegacyCameraCallback");
218            mHandlerThread.start();
219        }
220
221        public void close() {
222            mHandlerThread.quitSafely();
223        }
224
225        @Override
226        public void onDeviceError(final int errorCode, final CaptureResultExtras resultExtras) {
227            Message msg = getHandler().obtainMessage(CAMERA_ERROR,
228                /*arg1*/ errorCode, /*arg2*/ 0,
229                /*obj*/ resultExtras);
230            getHandler().sendMessage(msg);
231        }
232
233        @Override
234        public void onDeviceIdle() {
235            Message msg = getHandler().obtainMessage(CAMERA_IDLE);
236            getHandler().sendMessage(msg);
237        }
238
239        @Override
240        public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
241            Message msg = getHandler().obtainMessage(CAPTURE_STARTED,
242                    /*arg1*/ (int) (timestamp & 0xFFFFFFFFL),
243                    /*arg2*/ (int) ( (timestamp >> 32) & 0xFFFFFFFFL),
244                    /*obj*/ resultExtras);
245            getHandler().sendMessage(msg);
246        }
247
248        @Override
249        public void onResultReceived(final CameraMetadataNative result,
250                final CaptureResultExtras resultExtras) {
251            Object[] resultArray = new Object[] { result, resultExtras };
252            Message msg = getHandler().obtainMessage(RESULT_RECEIVED,
253                    /*obj*/ resultArray);
254            getHandler().sendMessage(msg);
255        }
256
257        @Override
258        public void onPrepared(int streamId) {
259            Message msg = getHandler().obtainMessage(PREPARED,
260                    /*arg1*/ streamId, /*arg2*/ 0);
261            getHandler().sendMessage(msg);
262        }
263
264        @Override
265        public IBinder asBinder() {
266            // This is solely intended to be used for in-process binding.
267            return null;
268        }
269
270        private Handler getHandler() {
271            if (mHandler == null) {
272                mHandler = new CallbackHandler(mHandlerThread.getLooper());
273            }
274            return mHandler;
275        }
276
277        private class CallbackHandler extends Handler {
278            public CallbackHandler(Looper l) {
279                super(l);
280            }
281
282            @Override
283            public void handleMessage(Message msg) {
284                try {
285                    switch (msg.what) {
286                        case CAMERA_ERROR: {
287                            int errorCode = msg.arg1;
288                            CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj;
289                            mCallbacks.onDeviceError(errorCode, resultExtras);
290                            break;
291                        }
292                        case CAMERA_IDLE:
293                            mCallbacks.onDeviceIdle();
294                            break;
295                        case CAPTURE_STARTED: {
296                            long timestamp = msg.arg2 & 0xFFFFFFFFL;
297                            timestamp = (timestamp << 32) | (msg.arg1 & 0xFFFFFFFFL);
298                            CaptureResultExtras resultExtras = (CaptureResultExtras) msg.obj;
299                            mCallbacks.onCaptureStarted(resultExtras, timestamp);
300                            break;
301                        }
302                        case RESULT_RECEIVED: {
303                            Object[] resultArray = (Object[]) msg.obj;
304                            CameraMetadataNative result = (CameraMetadataNative) resultArray[0];
305                            CaptureResultExtras resultExtras = (CaptureResultExtras) resultArray[1];
306                            mCallbacks.onResultReceived(result, resultExtras);
307                            break;
308                        }
309                        case PREPARED: {
310                            int streamId = msg.arg1;
311                            mCallbacks.onPrepared(streamId);
312                            break;
313                        }
314                        default:
315                            throw new IllegalArgumentException(
316                                "Unknown callback message " + msg.what);
317                    }
318                } catch (RemoteException e) {
319                    throw new IllegalStateException(
320                        "Received remote exception during camera callback " + msg.what, e);
321                }
322            }
323        }
324    }
325
326    public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks,
327                                                         int cameraId) {
328        if (DEBUG) {
329            Log.d(TAG, "Opening shim Camera device");
330        }
331
332        /*
333         * Put the camera open on a separate thread with its own looper; otherwise
334         * if the main thread is used then the callbacks might never get delivered
335         * (e.g. in CTS which run its own default looper only after tests)
336         */
337
338        CameraLooper init = new CameraLooper(cameraId);
339
340        CameraCallbackThread threadCallbacks = new CameraCallbackThread(callbacks);
341
342        // TODO: Make this async instead of blocking
343        int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS);
344        Camera legacyCamera = init.getCamera();
345
346        // Check errors old HAL initialization
347        LegacyExceptionUtils.throwOnServiceError(initErrors);
348
349        // Disable shutter sounds (this will work unconditionally) for api2 clients
350        legacyCamera.disableShutterSound();
351
352        CameraInfo info = new CameraInfo();
353        Camera.getCameraInfo(cameraId, info);
354
355        Camera.Parameters legacyParameters = null;
356        try {
357            legacyParameters = legacyCamera.getParameters();
358        } catch (RuntimeException e) {
359            throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION,
360                    "Unable to get initial parameters: " + e.getMessage());
361        }
362
363        CameraCharacteristics characteristics =
364                LegacyMetadataMapper.createCharacteristics(legacyParameters, info);
365        LegacyCameraDevice device = new LegacyCameraDevice(
366                cameraId, legacyCamera, characteristics, threadCallbacks);
367        return new CameraDeviceUserShim(cameraId, device, characteristics, init, threadCallbacks);
368    }
369
370    @Override
371    public void disconnect() {
372        if (DEBUG) {
373            Log.d(TAG, "disconnect called.");
374        }
375
376        if (mLegacyDevice.isClosed()) {
377            Log.w(TAG, "Cannot disconnect, device has already been closed.");
378        }
379
380        try {
381            mLegacyDevice.close();
382        } finally {
383            mCameraInit.close();
384            mCameraCallbacks.close();
385        }
386    }
387
388    @Override
389    public SubmitInfo submitRequest(CaptureRequest request, boolean streaming) {
390        if (DEBUG) {
391            Log.d(TAG, "submitRequest called.");
392        }
393        if (mLegacyDevice.isClosed()) {
394            String err = "Cannot submit request, device has been closed.";
395            Log.e(TAG, err);
396            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
397        }
398
399        synchronized(mConfigureLock) {
400            if (mConfiguring) {
401                String err = "Cannot submit request, configuration change in progress.";
402                Log.e(TAG, err);
403                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
404            }
405        }
406        return mLegacyDevice.submitRequest(request, streaming);
407    }
408
409    @Override
410    public SubmitInfo submitRequestList(CaptureRequest[] request, boolean streaming) {
411        if (DEBUG) {
412            Log.d(TAG, "submitRequestList called.");
413        }
414        if (mLegacyDevice.isClosed()) {
415            String err = "Cannot submit request list, device has been closed.";
416            Log.e(TAG, err);
417            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
418        }
419
420        synchronized(mConfigureLock) {
421            if (mConfiguring) {
422                String err = "Cannot submit request, configuration change in progress.";
423                Log.e(TAG, err);
424                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
425            }
426        }
427        return mLegacyDevice.submitRequestList(request, streaming);
428    }
429
430    @Override
431    public long cancelRequest(int requestId) {
432        if (DEBUG) {
433            Log.d(TAG, "cancelRequest called.");
434        }
435        if (mLegacyDevice.isClosed()) {
436            String err = "Cannot cancel request, device has been closed.";
437            Log.e(TAG, err);
438            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
439        }
440
441        synchronized(mConfigureLock) {
442            if (mConfiguring) {
443                String err = "Cannot cancel request, configuration change in progress.";
444                Log.e(TAG, err);
445                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
446            }
447        }
448        return mLegacyDevice.cancelRequest(requestId);
449    }
450
451    @Override
452    public void beginConfigure() {
453        if (DEBUG) {
454            Log.d(TAG, "beginConfigure called.");
455        }
456        if (mLegacyDevice.isClosed()) {
457            String err = "Cannot begin configure, device has been closed.";
458            Log.e(TAG, err);
459            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
460        }
461
462        synchronized(mConfigureLock) {
463            if (mConfiguring) {
464                String err = "Cannot begin configure, configuration change already in progress.";
465                Log.e(TAG, err);
466                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
467            }
468            mConfiguring = true;
469        }
470    }
471
472    @Override
473    public void endConfigure(boolean isConstrainedHighSpeed) {
474        if (DEBUG) {
475            Log.d(TAG, "endConfigure called.");
476        }
477        if (mLegacyDevice.isClosed()) {
478            String err = "Cannot end configure, device has been closed.";
479            Log.e(TAG, err);
480            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
481        }
482
483        SparseArray<Surface> surfaces = null;
484        synchronized(mConfigureLock) {
485            if (!mConfiguring) {
486                String err = "Cannot end configure, no configuration change in progress.";
487                Log.e(TAG, err);
488                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
489            }
490            if (mSurfaces != null) {
491                surfaces = mSurfaces.clone();
492            }
493            mConfiguring = false;
494        }
495        mLegacyDevice.configureOutputs(surfaces);
496    }
497
498    @Override
499    public void deleteStream(int streamId) {
500        if (DEBUG) {
501            Log.d(TAG, "deleteStream called.");
502        }
503        if (mLegacyDevice.isClosed()) {
504            String err = "Cannot delete stream, device has been closed.";
505            Log.e(TAG, err);
506            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
507        }
508
509        synchronized(mConfigureLock) {
510            if (!mConfiguring) {
511                String err = "Cannot delete stream, no configuration change in progress.";
512                Log.e(TAG, err);
513                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
514            }
515            int index = mSurfaces.indexOfKey(streamId);
516            if (index < 0) {
517                String err = "Cannot delete stream, stream id " + streamId + " doesn't exist.";
518                Log.e(TAG, err);
519                throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
520            }
521            mSurfaces.removeAt(index);
522        }
523    }
524
525    @Override
526    public int createStream(OutputConfiguration outputConfiguration) {
527        if (DEBUG) {
528            Log.d(TAG, "createStream called.");
529        }
530        if (mLegacyDevice.isClosed()) {
531            String err = "Cannot create stream, device has been closed.";
532            Log.e(TAG, err);
533            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
534        }
535
536        synchronized(mConfigureLock) {
537            if (!mConfiguring) {
538                String err = "Cannot create stream, beginConfigure hasn't been called yet.";
539                Log.e(TAG, err);
540                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
541            }
542            if (outputConfiguration.getRotation() != OutputConfiguration.ROTATION_0) {
543                String err = "Cannot create stream, stream rotation is not supported.";
544                Log.e(TAG, err);
545                throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
546            }
547            int id = ++mSurfaceIdCounter;
548            mSurfaces.put(id, outputConfiguration.getSurface());
549            return id;
550        }
551    }
552
553    @Override
554    public int createInputStream(int width, int height, int format) {
555        String err = "Creating input stream is not supported on legacy devices";
556        Log.e(TAG, err);
557        throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
558    }
559
560    @Override
561    public Surface getInputSurface() {
562        String err = "Getting input surface is not supported on legacy devices";
563        Log.e(TAG, err);
564        throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
565    }
566
567    @Override
568    public CameraMetadataNative createDefaultRequest(int templateId) {
569        if (DEBUG) {
570            Log.d(TAG, "createDefaultRequest called.");
571        }
572        if (mLegacyDevice.isClosed()) {
573            String err = "Cannot create default request, device has been closed.";
574            Log.e(TAG, err);
575            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
576        }
577
578        CameraMetadataNative template;
579        try {
580            template =
581                    LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId);
582        } catch (IllegalArgumentException e) {
583            String err = "createDefaultRequest - invalid templateId specified";
584            Log.e(TAG, err);
585            throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
586        }
587
588        return template;
589    }
590
591    @Override
592    public CameraMetadataNative getCameraInfo() {
593        if (DEBUG) {
594            Log.d(TAG, "getCameraInfo called.");
595        }
596        // TODO: implement getCameraInfo.
597        Log.e(TAG, "getCameraInfo unimplemented.");
598        return null;
599    }
600
601    @Override
602    public void waitUntilIdle() throws RemoteException {
603        if (DEBUG) {
604            Log.d(TAG, "waitUntilIdle called.");
605        }
606        if (mLegacyDevice.isClosed()) {
607            String err = "Cannot wait until idle, device has been closed.";
608            Log.e(TAG, err);
609            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
610        }
611
612        synchronized(mConfigureLock) {
613            if (mConfiguring) {
614                String err = "Cannot wait until idle, configuration change in progress.";
615                Log.e(TAG, err);
616                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
617            }
618        }
619        mLegacyDevice.waitUntilIdle();
620    }
621
622    @Override
623    public long flush() {
624        if (DEBUG) {
625            Log.d(TAG, "flush called.");
626        }
627        if (mLegacyDevice.isClosed()) {
628            String err = "Cannot flush, device has been closed.";
629            Log.e(TAG, err);
630            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
631        }
632
633        synchronized(mConfigureLock) {
634            if (mConfiguring) {
635                String err = "Cannot flush, configuration change in progress.";
636                Log.e(TAG, err);
637                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
638            }
639        }
640        return mLegacyDevice.flush();
641    }
642
643    public void prepare(int streamId) {
644        if (DEBUG) {
645            Log.d(TAG, "prepare called.");
646        }
647        if (mLegacyDevice.isClosed()) {
648            String err = "Cannot prepare stream, device has been closed.";
649            Log.e(TAG, err);
650            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
651        }
652
653        // LEGACY doesn't support actual prepare, just signal success right away
654        mCameraCallbacks.onPrepared(streamId);
655    }
656
657    public void prepare2(int maxCount, int streamId) {
658        // We don't support this in LEGACY mode.
659        prepare(streamId);
660    }
661
662    public void tearDown(int streamId) {
663        if (DEBUG) {
664            Log.d(TAG, "tearDown called.");
665        }
666        if (mLegacyDevice.isClosed()) {
667            String err = "Cannot tear down stream, device has been closed.";
668            Log.e(TAG, err);
669            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
670        }
671
672        // LEGACY doesn't support actual teardown, so just a no-op
673    }
674
675    @Override
676    public IBinder asBinder() {
677        // This is solely intended to be used for in-process binding.
678        return null;
679    }
680}
681