1/*
2 * Copyright (C) 2013 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.impl;
18
19import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE;
20
21import android.hardware.camera2.CameraAccessException;
22import android.hardware.camera2.CameraMetadata;
23import android.hardware.camera2.CameraCharacteristics;
24import android.hardware.camera2.CaptureRequest;
25import android.hardware.camera2.CaptureResult;
26import android.hardware.camera2.ICameraDeviceCallbacks;
27import android.hardware.camera2.ICameraDeviceUser;
28import android.hardware.camera2.utils.CameraBinderDecorator;
29import android.hardware.camera2.utils.CameraRuntimeException;
30import android.os.IBinder;
31import android.os.RemoteException;
32import android.os.Handler;
33import android.os.Looper;
34import android.util.Log;
35import android.util.SparseArray;
36import android.view.Surface;
37
38import java.util.ArrayList;
39import java.util.HashMap;
40import java.util.HashSet;
41import java.util.List;
42import java.util.Stack;
43
44/**
45 * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
46 */
47public class CameraDevice implements android.hardware.camera2.CameraDevice {
48
49    private final String TAG;
50    private final boolean DEBUG;
51
52    // TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
53    private ICameraDeviceUser mRemoteDevice;
54
55    private final Object mLock = new Object();
56    private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
57
58    private final StateListener mDeviceListener;
59    private final Handler mDeviceHandler;
60
61    private boolean mIdle = true;
62
63    private final SparseArray<CaptureListenerHolder> mCaptureListenerMap =
64            new SparseArray<CaptureListenerHolder>();
65
66    private final Stack<Integer> mRepeatingRequestIdStack = new Stack<Integer>();
67    // Map stream IDs to Surfaces
68    private final SparseArray<Surface> mConfiguredOutputs = new SparseArray<Surface>();
69
70    private final String mCameraId;
71
72    // Runnables for all state transitions, except error, which needs the
73    // error code argument
74
75    private final Runnable mCallOnOpened = new Runnable() {
76        public void run() {
77            if (!CameraDevice.this.isClosed()) {
78                mDeviceListener.onOpened(CameraDevice.this);
79            }
80        }
81    };
82
83    private final Runnable mCallOnUnconfigured = new Runnable() {
84        public void run() {
85            if (!CameraDevice.this.isClosed()) {
86                mDeviceListener.onUnconfigured(CameraDevice.this);
87            }
88        }
89    };
90
91    private final Runnable mCallOnActive = new Runnable() {
92        public void run() {
93            if (!CameraDevice.this.isClosed()) {
94                mDeviceListener.onActive(CameraDevice.this);
95            }
96        }
97    };
98
99    private final Runnable mCallOnBusy = new Runnable() {
100        public void run() {
101            if (!CameraDevice.this.isClosed()) {
102                mDeviceListener.onBusy(CameraDevice.this);
103            }
104        }
105    };
106
107    private final Runnable mCallOnClosed = new Runnable() {
108        public void run() {
109            if (!CameraDevice.this.isClosed()) {
110                mDeviceListener.onClosed(CameraDevice.this);
111            }
112        }
113    };
114
115    private final Runnable mCallOnIdle = new Runnable() {
116        public void run() {
117            if (!CameraDevice.this.isClosed()) {
118                mDeviceListener.onIdle(CameraDevice.this);
119            }
120        }
121    };
122
123    private final Runnable mCallOnDisconnected = new Runnable() {
124        public void run() {
125            if (!CameraDevice.this.isClosed()) {
126                mDeviceListener.onDisconnected(CameraDevice.this);
127            }
128        }
129    };
130
131    public CameraDevice(String cameraId, StateListener listener, Handler handler) {
132        if (cameraId == null || listener == null || handler == null) {
133            throw new IllegalArgumentException("Null argument given");
134        }
135        mCameraId = cameraId;
136        mDeviceListener = listener;
137        mDeviceHandler = handler;
138        TAG = String.format("CameraDevice-%s-JV", mCameraId);
139        DEBUG = Log.isLoggable(TAG, Log.DEBUG);
140    }
141
142    public CameraDeviceCallbacks getCallbacks() {
143        return mCallbacks;
144    }
145
146    public void setRemoteDevice(ICameraDeviceUser remoteDevice) {
147        // TODO: Move from decorator to direct binder-mediated exceptions
148        synchronized(mLock) {
149            mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice);
150
151            mDeviceHandler.post(mCallOnOpened);
152            mDeviceHandler.post(mCallOnUnconfigured);
153        }
154    }
155
156    @Override
157    public String getId() {
158        return mCameraId;
159    }
160
161    @Override
162    public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
163        // Treat a null input the same an empty list
164        if (outputs == null) {
165            outputs = new ArrayList<Surface>();
166        }
167        synchronized (mLock) {
168            checkIfCameraClosed();
169
170            HashSet<Surface> addSet = new HashSet<Surface>(outputs);    // Streams to create
171            List<Integer> deleteList = new ArrayList<Integer>();        // Streams to delete
172
173            // Determine which streams need to be created, which to be deleted
174            for (int i = 0; i < mConfiguredOutputs.size(); ++i) {
175                int streamId = mConfiguredOutputs.keyAt(i);
176                Surface s = mConfiguredOutputs.valueAt(i);
177
178                if (!outputs.contains(s)) {
179                    deleteList.add(streamId);
180                } else {
181                    addSet.remove(s);  // Don't create a stream previously created
182                }
183            }
184
185            mDeviceHandler.post(mCallOnBusy);
186            stopRepeating();
187
188            try {
189                mRemoteDevice.waitUntilIdle();
190
191                // TODO: mRemoteDevice.beginConfigure
192                // Delete all streams first (to free up HW resources)
193                for (Integer streamId : deleteList) {
194                    mRemoteDevice.deleteStream(streamId);
195                    mConfiguredOutputs.delete(streamId);
196                }
197
198                // Add all new streams
199                for (Surface s : addSet) {
200                    // TODO: remove width,height,format since we are ignoring
201                    // it.
202                    int streamId = mRemoteDevice.createStream(0, 0, 0, s);
203                    mConfiguredOutputs.put(streamId, s);
204                }
205
206                // TODO: mRemoteDevice.endConfigure
207            } catch (CameraRuntimeException e) {
208                if (e.getReason() == CAMERA_IN_USE) {
209                    throw new IllegalStateException("The camera is currently busy." +
210                            " You must wait until the previous operation completes.");
211                }
212
213                throw e.asChecked();
214            } catch (RemoteException e) {
215                // impossible
216                return;
217            }
218
219            if (outputs.size() > 0) {
220                mDeviceHandler.post(mCallOnIdle);
221            } else {
222                mDeviceHandler.post(mCallOnUnconfigured);
223            }
224        }
225    }
226
227    @Override
228    public CaptureRequest.Builder createCaptureRequest(int templateType)
229            throws CameraAccessException {
230        synchronized (mLock) {
231            checkIfCameraClosed();
232
233            CameraMetadataNative templatedRequest = new CameraMetadataNative();
234
235            try {
236                mRemoteDevice.createDefaultRequest(templateType, /* out */templatedRequest);
237            } catch (CameraRuntimeException e) {
238                throw e.asChecked();
239            } catch (RemoteException e) {
240                // impossible
241                return null;
242            }
243
244            CaptureRequest.Builder builder =
245                    new CaptureRequest.Builder(templatedRequest);
246
247            return builder;
248        }
249    }
250
251    @Override
252    public int capture(CaptureRequest request, CaptureListener listener, Handler handler)
253            throws CameraAccessException {
254        return submitCaptureRequest(request, listener, handler, /*streaming*/false);
255    }
256
257    @Override
258    public int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
259            Handler handler) throws CameraAccessException {
260        if (requests.isEmpty()) {
261            Log.w(TAG, "Capture burst request list is empty, do nothing!");
262            return -1;
263        }
264        // TODO
265        throw new UnsupportedOperationException("Burst capture implemented yet");
266
267    }
268
269    private int submitCaptureRequest(CaptureRequest request, CaptureListener listener,
270            Handler handler, boolean repeating) throws CameraAccessException {
271
272        // Need a valid handler, or current thread needs to have a looper, if
273        // listener is valid
274        if (listener != null) {
275            handler = checkHandler(handler);
276        }
277
278        synchronized (mLock) {
279            checkIfCameraClosed();
280            int requestId;
281
282            try {
283                requestId = mRemoteDevice.submitRequest(request, repeating);
284            } catch (CameraRuntimeException e) {
285                throw e.asChecked();
286            } catch (RemoteException e) {
287                // impossible
288                return -1;
289            }
290            if (listener != null) {
291                mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener, request,
292                        handler, repeating));
293            }
294
295            if (repeating) {
296                mRepeatingRequestIdStack.add(requestId);
297            }
298
299            if (mIdle) {
300                mDeviceHandler.post(mCallOnActive);
301            }
302            mIdle = false;
303
304            return requestId;
305        }
306    }
307
308    @Override
309    public int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
310            Handler handler) throws CameraAccessException {
311        return submitCaptureRequest(request, listener, handler, /*streaming*/true);
312    }
313
314    @Override
315    public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
316            Handler handler) throws CameraAccessException {
317        if (requests.isEmpty()) {
318            Log.w(TAG, "Set Repeating burst request list is empty, do nothing!");
319            return -1;
320        }
321        // TODO
322        throw new UnsupportedOperationException("Burst capture implemented yet");
323    }
324
325    @Override
326    public void stopRepeating() throws CameraAccessException {
327
328        synchronized (mLock) {
329            checkIfCameraClosed();
330            while (!mRepeatingRequestIdStack.isEmpty()) {
331                int requestId = mRepeatingRequestIdStack.pop();
332
333                try {
334                    mRemoteDevice.cancelRequest(requestId);
335                } catch (CameraRuntimeException e) {
336                    throw e.asChecked();
337                } catch (RemoteException e) {
338                    // impossible
339                    return;
340                }
341            }
342        }
343    }
344
345    @Override
346    public void waitUntilIdle() throws CameraAccessException {
347
348        synchronized (mLock) {
349            checkIfCameraClosed();
350            if (!mRepeatingRequestIdStack.isEmpty()) {
351                throw new IllegalStateException("Active repeating request ongoing");
352            }
353
354            try {
355                mRemoteDevice.waitUntilIdle();
356            } catch (CameraRuntimeException e) {
357                throw e.asChecked();
358            } catch (RemoteException e) {
359                // impossible
360                return;
361            }
362        }
363    }
364
365    @Override
366    public void flush() throws CameraAccessException {
367        synchronized (mLock) {
368            checkIfCameraClosed();
369
370            mDeviceHandler.post(mCallOnBusy);
371            try {
372                mRemoteDevice.flush();
373            } catch (CameraRuntimeException e) {
374                throw e.asChecked();
375            } catch (RemoteException e) {
376                // impossible
377                return;
378            }
379        }
380    }
381
382    @Override
383    public void close() {
384        synchronized (mLock) {
385
386            try {
387                if (mRemoteDevice != null) {
388                    mRemoteDevice.disconnect();
389                }
390            } catch (CameraRuntimeException e) {
391                Log.e(TAG, "Exception while closing: ", e.asChecked());
392            } catch (RemoteException e) {
393                // impossible
394            }
395
396            if (mRemoteDevice != null) {
397                mDeviceHandler.post(mCallOnClosed);
398            }
399
400            mRemoteDevice = null;
401        }
402    }
403
404    @Override
405    protected void finalize() throws Throwable {
406        try {
407            close();
408        }
409        finally {
410            super.finalize();
411        }
412    }
413
414    static class CaptureListenerHolder {
415
416        private final boolean mRepeating;
417        private final CaptureListener mListener;
418        private final CaptureRequest mRequest;
419        private final Handler mHandler;
420
421        CaptureListenerHolder(CaptureListener listener, CaptureRequest request, Handler handler,
422                boolean repeating) {
423            if (listener == null || handler == null) {
424                throw new UnsupportedOperationException(
425                    "Must have a valid handler and a valid listener");
426            }
427            mRepeating = repeating;
428            mHandler = handler;
429            mRequest = request;
430            mListener = listener;
431        }
432
433        public boolean isRepeating() {
434            return mRepeating;
435        }
436
437        public CaptureListener getListener() {
438            return mListener;
439        }
440
441        public CaptureRequest getRequest() {
442            return mRequest;
443        }
444
445        public Handler getHandler() {
446            return mHandler;
447        }
448
449    }
450
451    public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
452
453        //
454        // Constants below need to be kept up-to-date with
455        // frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
456        //
457
458        //
459        // Error codes for onCameraError
460        //
461
462        /**
463         * Camera has been disconnected
464         */
465        static final int ERROR_CAMERA_DISCONNECTED = 0;
466
467        /**
468         * Camera has encountered a device-level error
469         * Matches CameraDevice.StateListener#ERROR_CAMERA_DEVICE
470         */
471        static final int ERROR_CAMERA_DEVICE = 1;
472
473        /**
474         * Camera has encountered a service-level error
475         * Matches CameraDevice.StateListener#ERROR_CAMERA_SERVICE
476         */
477        static final int ERROR_CAMERA_SERVICE = 2;
478
479        @Override
480        public IBinder asBinder() {
481            return this;
482        }
483
484        @Override
485        public void onCameraError(final int errorCode) {
486            Runnable r = null;
487            if (isClosed()) return;
488
489            synchronized(mLock) {
490                switch (errorCode) {
491                    case ERROR_CAMERA_DISCONNECTED:
492                        r = mCallOnDisconnected;
493                        break;
494                    default:
495                        Log.e(TAG, "Unknown error from camera device: " + errorCode);
496                        // no break
497                    case ERROR_CAMERA_DEVICE:
498                    case ERROR_CAMERA_SERVICE:
499                        r = new Runnable() {
500                            public void run() {
501                                if (!CameraDevice.this.isClosed()) {
502                                    mDeviceListener.onError(CameraDevice.this, errorCode);
503                                }
504                            }
505                        };
506                        break;
507                }
508                CameraDevice.this.mDeviceHandler.post(r);
509            }
510        }
511
512        @Override
513        public void onCameraIdle() {
514            if (isClosed()) return;
515
516            if (DEBUG) {
517                Log.d(TAG, "Camera now idle");
518            }
519            synchronized (mLock) {
520                if (!CameraDevice.this.mIdle) {
521                    CameraDevice.this.mDeviceHandler.post(mCallOnIdle);
522                }
523                CameraDevice.this.mIdle = true;
524            }
525        }
526
527        @Override
528        public void onCaptureStarted(int requestId, final long timestamp) {
529            if (DEBUG) {
530                Log.d(TAG, "Capture started for id " + requestId);
531            }
532            final CaptureListenerHolder holder;
533
534            // Get the listener for this frame ID, if there is one
535            synchronized (mLock) {
536                holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
537            }
538
539            if (holder == null) {
540                return;
541            }
542
543            if (isClosed()) return;
544
545            // Dispatch capture start notice
546            holder.getHandler().post(
547                new Runnable() {
548                    public void run() {
549                        if (!CameraDevice.this.isClosed()) {
550                            holder.getListener().onCaptureStarted(
551                                CameraDevice.this,
552                                holder.getRequest(),
553                                timestamp);
554                        }
555                    }
556                });
557        }
558
559        @Override
560        public void onResultReceived(int requestId, CameraMetadataNative result)
561                throws RemoteException {
562            if (DEBUG) {
563                Log.d(TAG, "Received result for id " + requestId);
564            }
565            final CaptureListenerHolder holder;
566
567            synchronized (mLock) {
568                // TODO: move this whole map into this class to make it more testable,
569                //        exposing the methods necessary like subscribeToRequest, unsubscribe..
570                // TODO: make class static class
571
572                holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
573
574                // Clean up listener once we no longer expect to see it.
575
576                // TODO: how to handle repeating listeners?
577                // we probably want cancelRequest to return # of times it already enqueued and
578                // keep a counter.
579                if (holder != null && !holder.isRepeating()) {
580                    CameraDevice.this.mCaptureListenerMap.remove(requestId);
581                }
582            }
583
584            // Check if we have a listener for this
585            if (holder == null) {
586                return;
587            }
588
589            if (isClosed()) return;
590
591            final CaptureRequest request = holder.getRequest();
592            final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId);
593
594            holder.getHandler().post(
595                new Runnable() {
596                    @Override
597                    public void run() {
598                        if (!CameraDevice.this.isClosed()){
599                            holder.getListener().onCaptureCompleted(
600                                CameraDevice.this,
601                                request,
602                                resultAsCapture);
603                        }
604                    }
605                });
606        }
607
608    }
609
610    /**
611     * Default handler management. If handler is null, get the current thread's
612     * Looper to create a Handler with. If no looper exists, throw exception.
613     */
614    private Handler checkHandler(Handler handler) {
615        if (handler == null) {
616            Looper looper = Looper.myLooper();
617            if (looper == null) {
618                throw new IllegalArgumentException(
619                    "No handler given, and current thread has no looper!");
620            }
621            handler = new Handler(looper);
622        }
623        return handler;
624    }
625
626    private void checkIfCameraClosed() {
627        if (mRemoteDevice == null) {
628            throw new IllegalStateException("CameraDevice was already closed");
629        }
630    }
631
632    private boolean isClosed() {
633        synchronized(mLock) {
634            return (mRemoteDevice == null);
635        }
636    }
637}
638