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