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