CameraDeviceUserShim.java revision df6242e374b81e802a38cb891477f05d3e4b3cbc
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.utils.CameraBinderDecorator;
29import android.hardware.camera2.utils.CameraRuntimeException;
30import android.os.ConditionVariable;
31import android.os.IBinder;
32import android.os.Looper;
33import android.os.RemoteException;
34import android.util.Log;
35import android.util.SparseArray;
36import android.view.Surface;
37
38import java.util.ArrayList;
39import java.util.List;
40
41/**
42 * Compatibility implementation of the Camera2 API binder interface.
43 *
44 * <p>
45 * This is intended to be called from the same process as client
46 * {@link android.hardware.camera2.CameraDevice}, and wraps a
47 * {@link android.hardware.camera2.legacy.LegacyCameraDevice} that emulates Camera2 service using
48 * the Camera1 API.
49 * </p>
50 *
51 * <p>
52 * Keep up to date with ICameraDeviceUser.aidl.
53 * </p>
54 */
55public class CameraDeviceUserShim implements ICameraDeviceUser {
56    private static final String TAG = "CameraDeviceUserShim";
57
58    private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
59    private static final int OPEN_CAMERA_TIMEOUT_MS = 5000; // 5 sec (same as api1 cts timeout)
60
61    private final LegacyCameraDevice mLegacyDevice;
62
63    private final Object mConfigureLock = new Object();
64    private int mSurfaceIdCounter;
65    private boolean mConfiguring;
66    private final SparseArray<Surface> mSurfaces;
67    private final CameraCharacteristics mCameraCharacteristics;
68    private final CameraLooper mCameraInit;
69
70    protected CameraDeviceUserShim(int cameraId, LegacyCameraDevice legacyCamera,
71            CameraCharacteristics characteristics, CameraLooper cameraInit) {
72        mLegacyDevice = legacyCamera;
73        mConfiguring = false;
74        mSurfaces = new SparseArray<Surface>();
75        mCameraCharacteristics = characteristics;
76        mCameraInit = cameraInit;
77
78        mSurfaceIdCounter = 0;
79    }
80
81    /**
82     * Create a separate looper/thread for the camera to run on; open the camera.
83     *
84     * <p>Since the camera automatically latches on to the current thread's looper,
85     * it's important that we have our own thread with our own looper to guarantee
86     * that the camera callbacks get correctly posted to our own thread.</p>
87     */
88    private static class CameraLooper implements Runnable, AutoCloseable {
89        private final int mCameraId;
90        private Looper mLooper;
91        private volatile int mInitErrors;
92        private final Camera mCamera = Camera.openUninitialized();
93        private final ConditionVariable mStartDone = new ConditionVariable();
94        private final Thread mThread;
95
96        /**
97         * Spin up a new thread, immediately open the camera in the background.
98         *
99         * <p>Use {@link #waitForOpen} to block until the camera is finished opening.</p>
100         *
101         * @param cameraId numeric camera Id
102         *
103         * @see #waitForOpen
104         */
105        public CameraLooper(int cameraId) {
106            mCameraId = cameraId;
107
108            mThread = new Thread(this);
109            mThread.start();
110        }
111
112        public Camera getCamera() {
113            return mCamera;
114        }
115
116        @Override
117        public void run() {
118            // Set up a looper to be used by camera.
119            Looper.prepare();
120
121            // Save the looper so that we can terminate this thread
122            // after we are done with it.
123            mLooper = Looper.myLooper();
124            mInitErrors = mCamera.cameraInitUnspecified(mCameraId);
125
126            mStartDone.open();
127            Looper.loop();  // Blocks forever until #close is called.
128        }
129
130        /**
131         * Quit the looper safely; then join until the thread shuts down.
132         */
133        @Override
134        public void close() {
135            if (mLooper == null) {
136                return;
137            }
138
139            mLooper.quitSafely();
140            try {
141                mThread.join();
142            } catch (InterruptedException e) {
143                throw new AssertionError(e);
144            }
145
146            mLooper = null;
147        }
148
149        /**
150         * Block until the camera opens; then return its initialization error code (if any).
151         *
152         * @param timeoutMs timeout in milliseconds
153         *
154         * @return int error code
155         *
156         * @throws CameraRuntimeException if the camera open times out with ({@code CAMERA_ERROR})
157         */
158        public int waitForOpen(int timeoutMs) {
159            // Block until the camera is open asynchronously
160            if (!mStartDone.block(timeoutMs)) {
161                Log.e(TAG, "waitForOpen - Camera failed to open after timeout of "
162                        + OPEN_CAMERA_TIMEOUT_MS + " ms");
163                try {
164                    mCamera.release();
165                } catch (RuntimeException e) {
166                    Log.e(TAG, "connectBinderShim - Failed to release camera after timeout ", e);
167                }
168
169                throw new CameraRuntimeException(CameraAccessException.CAMERA_ERROR);
170            }
171
172            return mInitErrors;
173        }
174    }
175
176    public static CameraDeviceUserShim connectBinderShim(ICameraDeviceCallbacks callbacks,
177                                                         int cameraId) {
178        if (DEBUG) {
179            Log.d(TAG, "Opening shim Camera device");
180        }
181
182        /*
183         * Put the camera open on a separate thread with its own looper; otherwise
184         * if the main thread is used then the callbacks might never get delivered
185         * (e.g. in CTS which run its own default looper only after tests)
186         */
187
188        CameraLooper init = new CameraLooper(cameraId);
189
190        // TODO: Make this async instead of blocking
191        int initErrors = init.waitForOpen(OPEN_CAMERA_TIMEOUT_MS);
192        Camera legacyCamera = init.getCamera();
193
194        // Check errors old HAL initialization
195        CameraBinderDecorator.throwOnError(initErrors);
196
197        CameraInfo info = new CameraInfo();
198        Camera.getCameraInfo(cameraId, info);
199
200        CameraCharacteristics characteristics =
201                LegacyMetadataMapper.createCharacteristics(legacyCamera.getParameters(), info);
202        LegacyCameraDevice device = new LegacyCameraDevice(
203                cameraId, legacyCamera, characteristics, callbacks);
204        return new CameraDeviceUserShim(cameraId, device, characteristics, init);
205    }
206
207    @Override
208    public void disconnect() {
209        if (DEBUG) {
210            Log.d(TAG, "disconnect called.");
211        }
212
213        try {
214            mLegacyDevice.close();
215        } finally {
216            mCameraInit.close();
217        }
218    }
219
220    @Override
221    public int submitRequest(CaptureRequest request, boolean streaming,
222                             /*out*/LongParcelable lastFrameNumber) {
223        if (DEBUG) {
224            Log.d(TAG, "submitRequest called.");
225        }
226        synchronized(mConfigureLock) {
227            if (mConfiguring) {
228                Log.e(TAG, "Cannot submit request, configuration change in progress.");
229                return CameraBinderDecorator.INVALID_OPERATION;
230            }
231        }
232        return mLegacyDevice.submitRequest(request, streaming, lastFrameNumber);
233    }
234
235    @Override
236    public int submitRequestList(List<CaptureRequest> request, boolean streaming,
237                                 /*out*/LongParcelable lastFrameNumber) {
238        if (DEBUG) {
239            Log.d(TAG, "submitRequestList called.");
240        }
241        synchronized(mConfigureLock) {
242            if (mConfiguring) {
243                Log.e(TAG, "Cannot submit request, configuration change in progress.");
244                return CameraBinderDecorator.INVALID_OPERATION;
245            }
246        }
247        return mLegacyDevice.submitRequestList(request, streaming, lastFrameNumber);
248    }
249
250    @Override
251    public int cancelRequest(int requestId, /*out*/LongParcelable lastFrameNumber) {
252        if (DEBUG) {
253            Log.d(TAG, "cancelRequest called.");
254        }
255        synchronized(mConfigureLock) {
256            if (mConfiguring) {
257                Log.e(TAG, "Cannot cancel request, configuration change in progress.");
258                return CameraBinderDecorator.INVALID_OPERATION;
259            }
260        }
261        long lastFrame = mLegacyDevice.cancelRequest(requestId);
262        lastFrameNumber.setNumber(lastFrame);
263        return CameraBinderDecorator.NO_ERROR;
264    }
265
266    @Override
267    public int beginConfigure() {
268        if (DEBUG) {
269            Log.d(TAG, "beginConfigure called.");
270        }
271        synchronized(mConfigureLock) {
272            if (mConfiguring) {
273                Log.e(TAG, "Cannot begin configure, configuration change already in progress.");
274                return CameraBinderDecorator.INVALID_OPERATION;
275            }
276            mConfiguring = true;
277        }
278        return CameraBinderDecorator.NO_ERROR;
279    }
280
281    @Override
282    public int endConfigure() {
283        if (DEBUG) {
284            Log.d(TAG, "endConfigure called.");
285        }
286        ArrayList<Surface> surfaces = null;
287        synchronized(mConfigureLock) {
288            if (!mConfiguring) {
289                Log.e(TAG, "Cannot end configure, no configuration change in progress.");
290                return CameraBinderDecorator.INVALID_OPERATION;
291            }
292            int numSurfaces = mSurfaces.size();
293            if (numSurfaces > 0) {
294                surfaces = new ArrayList<>();
295                for (int i = 0; i < numSurfaces; ++i) {
296                    surfaces.add(mSurfaces.valueAt(i));
297                }
298            }
299            mConfiguring = false;
300        }
301        return mLegacyDevice.configureOutputs(surfaces);
302    }
303
304    @Override
305    public int deleteStream(int streamId) {
306        if (DEBUG) {
307            Log.d(TAG, "deleteStream called.");
308        }
309        synchronized(mConfigureLock) {
310            if (!mConfiguring) {
311                Log.e(TAG, "Cannot delete stream, beginConfigure hasn't been called yet.");
312                return CameraBinderDecorator.INVALID_OPERATION;
313            }
314            int index = mSurfaces.indexOfKey(streamId);
315            if (index < 0) {
316                Log.e(TAG, "Cannot delete stream, stream id " + streamId + " doesn't exist.");
317                return CameraBinderDecorator.BAD_VALUE;
318            }
319            mSurfaces.removeAt(index);
320        }
321        return CameraBinderDecorator.NO_ERROR;
322    }
323
324    @Override
325    public int createStream(int width, int height, int format, Surface surface) {
326        if (DEBUG) {
327            Log.d(TAG, "createStream called.");
328        }
329        synchronized(mConfigureLock) {
330            if (!mConfiguring) {
331                Log.e(TAG, "Cannot create stream, beginConfigure hasn't been called yet.");
332                return CameraBinderDecorator.INVALID_OPERATION;
333            }
334            int id = ++mSurfaceIdCounter;
335            mSurfaces.put(id, surface);
336            return id;
337        }
338    }
339
340    @Override
341    public int createDefaultRequest(int templateId, /*out*/CameraMetadataNative request) {
342        if (DEBUG) {
343            Log.d(TAG, "createDefaultRequest called.");
344        }
345
346        CameraMetadataNative template;
347        try {
348            template =
349                    LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId);
350        } catch (IllegalArgumentException e) {
351            Log.e(TAG, "createDefaultRequest - invalid templateId specified");
352            return CameraBinderDecorator.BAD_VALUE;
353        }
354
355        request.swap(template);
356        return CameraBinderDecorator.NO_ERROR;
357    }
358
359    @Override
360    public int getCameraInfo(/*out*/CameraMetadataNative info) {
361        if (DEBUG) {
362            Log.d(TAG, "getCameraInfo called.");
363        }
364        // TODO: implement getCameraInfo.
365        Log.e(TAG, "getCameraInfo unimplemented.");
366        return CameraBinderDecorator.NO_ERROR;
367    }
368
369    @Override
370    public int waitUntilIdle() throws RemoteException {
371        if (DEBUG) {
372            Log.d(TAG, "waitUntilIdle called.");
373        }
374        synchronized(mConfigureLock) {
375            if (mConfiguring) {
376                Log.e(TAG, "Cannot wait until idle, configuration change in progress.");
377                return CameraBinderDecorator.INVALID_OPERATION;
378            }
379        }
380        mLegacyDevice.waitUntilIdle();
381        return CameraBinderDecorator.NO_ERROR;
382    }
383
384    @Override
385    public int flush(/*out*/LongParcelable lastFrameNumber) {
386        if (DEBUG) {
387            Log.d(TAG, "flush called.");
388        }
389        synchronized(mConfigureLock) {
390            if (mConfiguring) {
391                Log.e(TAG, "Cannot flush, configuration change in progress.");
392                return CameraBinderDecorator.INVALID_OPERATION;
393            }
394        }
395        // TODO: implement flush.
396        return CameraBinderDecorator.NO_ERROR;
397    }
398
399    @Override
400    public IBinder asBinder() {
401        // This is solely intended to be used for in-process binding.
402        return null;
403    }
404}
405