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.CaptureRequest;
23import android.hardware.camera2.CaptureResult;
24import android.hardware.camera2.ICameraDeviceCallbacks;
25import android.hardware.camera2.ICameraDeviceUser;
26import android.hardware.camera2.utils.CameraBinderDecorator;
27import android.hardware.camera2.utils.CameraRuntimeException;
28import android.os.Handler;
29import android.os.IBinder;
30import android.os.Looper;
31import android.os.RemoteException;
32import android.util.Log;
33import android.util.SparseArray;
34import android.view.Surface;
35
36import java.util.ArrayList;
37import java.util.HashSet;
38import java.util.Iterator;
39import java.util.List;
40
41/**
42 * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
43 */
44public class CameraDevice implements android.hardware.camera2.CameraDevice {
45
46    private final String TAG;
47    private final boolean DEBUG;
48
49    private static final int REQUEST_ID_NONE = -1;
50
51    // TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
52    private ICameraDeviceUser mRemoteDevice;
53
54    private final Object mLock = new Object();
55    private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
56
57    private final StateListener mDeviceListener;
58    private final Handler mDeviceHandler;
59
60    private boolean mIdle = true;
61
62    private final SparseArray<CaptureListenerHolder> mCaptureListenerMap =
63            new SparseArray<CaptureListenerHolder>();
64
65    private int mRepeatingRequestId = REQUEST_ID_NONE;
66    private final ArrayList<Integer> mRepeatingRequestIdDeletedList = new ArrayList<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                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            if (repeating) {
283                stopRepeating();
284            }
285
286            try {
287                requestId = mRemoteDevice.submitRequest(request, repeating);
288            } catch (CameraRuntimeException e) {
289                throw e.asChecked();
290            } catch (RemoteException e) {
291                // impossible
292                return -1;
293            }
294            if (listener != null) {
295                mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener, request,
296                        handler, repeating));
297            }
298
299            if (repeating) {
300                mRepeatingRequestId = requestId;
301            }
302
303            if (mIdle) {
304                mDeviceHandler.post(mCallOnActive);
305            }
306            mIdle = false;
307
308            return requestId;
309        }
310    }
311
312    @Override
313    public int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
314            Handler handler) throws CameraAccessException {
315        return submitCaptureRequest(request, listener, handler, /*streaming*/true);
316    }
317
318    @Override
319    public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
320            Handler handler) throws CameraAccessException {
321        if (requests.isEmpty()) {
322            Log.w(TAG, "Set Repeating burst request list is empty, do nothing!");
323            return -1;
324        }
325        // TODO
326        throw new UnsupportedOperationException("Burst capture implemented yet");
327    }
328
329    @Override
330    public void stopRepeating() throws CameraAccessException {
331
332        synchronized (mLock) {
333            checkIfCameraClosed();
334            if (mRepeatingRequestId != REQUEST_ID_NONE) {
335
336                int requestId = mRepeatingRequestId;
337                mRepeatingRequestId = REQUEST_ID_NONE;
338
339                // Queue for deletion after in-flight requests finish
340                mRepeatingRequestIdDeletedList.add(requestId);
341
342                try {
343                    mRemoteDevice.cancelRequest(requestId);
344                } catch (CameraRuntimeException e) {
345                    throw e.asChecked();
346                } catch (RemoteException e) {
347                    // impossible
348                    return;
349                }
350            }
351        }
352    }
353
354    @Override
355    public void waitUntilIdle() throws CameraAccessException {
356
357        synchronized (mLock) {
358            checkIfCameraClosed();
359            if (mRepeatingRequestId != REQUEST_ID_NONE) {
360                throw new IllegalStateException("Active repeating request ongoing");
361            }
362
363            try {
364                mRemoteDevice.waitUntilIdle();
365            } catch (CameraRuntimeException e) {
366                throw e.asChecked();
367            } catch (RemoteException e) {
368                // impossible
369                return;
370            }
371
372            mRepeatingRequestId = REQUEST_ID_NONE;
373            mRepeatingRequestIdDeletedList.clear();
374            mCaptureListenerMap.clear();
375        }
376    }
377
378    @Override
379    public void flush() throws CameraAccessException {
380        synchronized (mLock) {
381            checkIfCameraClosed();
382
383            mDeviceHandler.post(mCallOnBusy);
384            try {
385                mRemoteDevice.flush();
386            } catch (CameraRuntimeException e) {
387                throw e.asChecked();
388            } catch (RemoteException e) {
389                // impossible
390                return;
391            }
392        }
393    }
394
395    @Override
396    public void close() {
397        synchronized (mLock) {
398
399            try {
400                if (mRemoteDevice != null) {
401                    mRemoteDevice.disconnect();
402                }
403            } catch (CameraRuntimeException e) {
404                Log.e(TAG, "Exception while closing: ", e.asChecked());
405            } catch (RemoteException e) {
406                // impossible
407            }
408
409            if (mRemoteDevice != null) {
410                mDeviceHandler.post(mCallOnClosed);
411            }
412
413            mRemoteDevice = null;
414        }
415    }
416
417    @Override
418    protected void finalize() throws Throwable {
419        try {
420            close();
421        }
422        finally {
423            super.finalize();
424        }
425    }
426
427    static class CaptureListenerHolder {
428
429        private final boolean mRepeating;
430        private final CaptureListener mListener;
431        private final CaptureRequest mRequest;
432        private final Handler mHandler;
433
434        CaptureListenerHolder(CaptureListener listener, CaptureRequest request, Handler handler,
435                boolean repeating) {
436            if (listener == null || handler == null) {
437                throw new UnsupportedOperationException(
438                    "Must have a valid handler and a valid listener");
439            }
440            mRepeating = repeating;
441            mHandler = handler;
442            mRequest = request;
443            mListener = listener;
444        }
445
446        public boolean isRepeating() {
447            return mRepeating;
448        }
449
450        public CaptureListener getListener() {
451            return mListener;
452        }
453
454        public CaptureRequest getRequest() {
455            return mRequest;
456        }
457
458        public Handler getHandler() {
459            return mHandler;
460        }
461
462    }
463
464    public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
465
466        //
467        // Constants below need to be kept up-to-date with
468        // frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
469        //
470
471        //
472        // Error codes for onCameraError
473        //
474
475        /**
476         * Camera has been disconnected
477         */
478        static final int ERROR_CAMERA_DISCONNECTED = 0;
479
480        /**
481         * Camera has encountered a device-level error
482         * Matches CameraDevice.StateListener#ERROR_CAMERA_DEVICE
483         */
484        static final int ERROR_CAMERA_DEVICE = 1;
485
486        /**
487         * Camera has encountered a service-level error
488         * Matches CameraDevice.StateListener#ERROR_CAMERA_SERVICE
489         */
490        static final int ERROR_CAMERA_SERVICE = 2;
491
492        @Override
493        public IBinder asBinder() {
494            return this;
495        }
496
497        @Override
498        public void onCameraError(final int errorCode) {
499            Runnable r = null;
500            if (isClosed()) return;
501
502            synchronized(mLock) {
503                switch (errorCode) {
504                    case ERROR_CAMERA_DISCONNECTED:
505                        r = mCallOnDisconnected;
506                        break;
507                    default:
508                        Log.e(TAG, "Unknown error from camera device: " + errorCode);
509                        // no break
510                    case ERROR_CAMERA_DEVICE:
511                    case ERROR_CAMERA_SERVICE:
512                        r = new Runnable() {
513                            public void run() {
514                                if (!CameraDevice.this.isClosed()) {
515                                    mDeviceListener.onError(CameraDevice.this, errorCode);
516                                }
517                            }
518                        };
519                        break;
520                }
521                CameraDevice.this.mDeviceHandler.post(r);
522            }
523        }
524
525        @Override
526        public void onCameraIdle() {
527            if (isClosed()) return;
528
529            if (DEBUG) {
530                Log.d(TAG, "Camera now idle");
531            }
532            synchronized (mLock) {
533                if (!CameraDevice.this.mIdle) {
534                    CameraDevice.this.mDeviceHandler.post(mCallOnIdle);
535                }
536                CameraDevice.this.mIdle = true;
537            }
538        }
539
540        @Override
541        public void onCaptureStarted(int requestId, final long timestamp) {
542            if (DEBUG) {
543                Log.d(TAG, "Capture started for id " + requestId);
544            }
545            final CaptureListenerHolder holder;
546
547            // Get the listener for this frame ID, if there is one
548            synchronized (mLock) {
549                holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
550            }
551
552            if (holder == null) {
553                return;
554            }
555
556            if (isClosed()) return;
557
558            // Dispatch capture start notice
559            holder.getHandler().post(
560                new Runnable() {
561                    public void run() {
562                        if (!CameraDevice.this.isClosed()) {
563                            holder.getListener().onCaptureStarted(
564                                CameraDevice.this,
565                                holder.getRequest(),
566                                timestamp);
567                        }
568                    }
569                });
570        }
571
572        @Override
573        public void onResultReceived(int requestId, CameraMetadataNative result)
574                throws RemoteException {
575            if (DEBUG) {
576                Log.d(TAG, "Received result for id " + requestId);
577            }
578            final CaptureListenerHolder holder;
579
580            Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
581            boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
582
583            synchronized (mLock) {
584                // TODO: move this whole map into this class to make it more testable,
585                //        exposing the methods necessary like subscribeToRequest, unsubscribe..
586                // TODO: make class static class
587
588                holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
589
590                // Clean up listener once we no longer expect to see it.
591                if (holder != null && !holder.isRepeating() && !quirkIsPartialResult) {
592                    CameraDevice.this.mCaptureListenerMap.remove(requestId);
593                }
594
595                // TODO: add 'capture sequence completed' callback to the
596                // service, and clean up repeating requests there instead.
597
598                // If we received a result for a repeating request and have
599                // prior repeating requests queued for deletion, remove those
600                // requests from mCaptureListenerMap.
601                if (holder != null && holder.isRepeating() && !quirkIsPartialResult
602                        && mRepeatingRequestIdDeletedList.size() > 0) {
603                    Iterator<Integer> iter = mRepeatingRequestIdDeletedList.iterator();
604                    while (iter.hasNext()) {
605                        int deletedRequestId = iter.next();
606                        if (deletedRequestId < requestId) {
607                            CameraDevice.this.mCaptureListenerMap.remove(deletedRequestId);
608                            iter.remove();
609                        }
610                    }
611                }
612
613            }
614
615            // Check if we have a listener for this
616            if (holder == null) {
617                return;
618            }
619
620            if (isClosed()) return;
621
622            final CaptureRequest request = holder.getRequest();
623            final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId);
624
625            Runnable resultDispatch = null;
626
627            // Either send a partial result or the final capture completed result
628            if (quirkIsPartialResult) {
629                // Partial result
630                resultDispatch = new Runnable() {
631                    @Override
632                    public void run() {
633                        if (!CameraDevice.this.isClosed()){
634                            holder.getListener().onCapturePartial(
635                                CameraDevice.this,
636                                request,
637                                resultAsCapture);
638                        }
639                    }
640                };
641            } else {
642                // Final capture result
643                resultDispatch = new Runnable() {
644                    @Override
645                    public void run() {
646                        if (!CameraDevice.this.isClosed()){
647                            holder.getListener().onCaptureCompleted(
648                                CameraDevice.this,
649                                request,
650                                resultAsCapture);
651                        }
652                    }
653                };
654            }
655
656            holder.getHandler().post(resultDispatch);
657        }
658
659    }
660
661    /**
662     * Default handler management. If handler is null, get the current thread's
663     * Looper to create a Handler with. If no looper exists, throw exception.
664     */
665    private Handler checkHandler(Handler handler) {
666        if (handler == null) {
667            Looper looper = Looper.myLooper();
668            if (looper == null) {
669                throw new IllegalArgumentException(
670                    "No handler given, and current thread has no looper!");
671            }
672            handler = new Handler(looper);
673        }
674        return handler;
675    }
676
677    private void checkIfCameraClosed() {
678        if (mRemoteDevice == null) {
679            throw new IllegalStateException("CameraDevice was already closed");
680        }
681    }
682
683    private boolean isClosed() {
684        synchronized(mLock) {
685            return (mRemoteDevice == null);
686        }
687    }
688}
689