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