CameraDeviceImpl.java revision 49b2b135105e5ca5dc9547f4c6de473bebad647d
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.CameraCaptureSession;
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.TotalCaptureResult;
29import android.hardware.camera2.utils.CameraBinderDecorator;
30import android.hardware.camera2.utils.CameraRuntimeException;
31import android.hardware.camera2.utils.CloseableLock;
32import android.hardware.camera2.utils.CloseableLock.ScopedLock;
33import android.hardware.camera2.utils.LongParcelable;
34import android.os.Handler;
35import android.os.IBinder;
36import android.os.Looper;
37import android.os.RemoteException;
38import android.util.Log;
39import android.util.SparseArray;
40import android.view.Surface;
41
42import java.util.AbstractMap.SimpleEntry;
43import java.util.ArrayList;
44import java.util.HashSet;
45import java.util.Iterator;
46import java.util.List;
47import java.util.TreeSet;
48
49/**
50 * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
51 */
52public class CameraDeviceImpl extends android.hardware.camera2.CameraDevice {
53
54    private final String TAG;
55    private final boolean DEBUG;
56
57    private static final int REQUEST_ID_NONE = -1;
58
59    // TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
60    private ICameraDeviceUser mRemoteDevice;
61
62    private final CloseableLock mCloseLock;
63    private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
64
65    private final StateListener mDeviceListener;
66    private volatile StateListener mSessionStateListener;
67    private final Handler mDeviceHandler;
68
69    private volatile boolean mClosing = false;
70    private boolean mInError = false;
71    private boolean mIdle = true;
72
73    /** map request IDs to listener/request data */
74    private final SparseArray<CaptureListenerHolder> mCaptureListenerMap =
75            new SparseArray<CaptureListenerHolder>();
76
77    private int mRepeatingRequestId = REQUEST_ID_NONE;
78    private final ArrayList<Integer> mRepeatingRequestIdDeletedList = new ArrayList<Integer>();
79    // Map stream IDs to Surfaces
80    private final SparseArray<Surface> mConfiguredOutputs = new SparseArray<Surface>();
81
82    private final String mCameraId;
83    private final CameraCharacteristics mCharacteristics;
84
85    /**
86     * A list tracking request and its expected last frame.
87     * Updated when calling ICameraDeviceUser methods.
88     */
89    private final List<SimpleEntry</*frameNumber*/Long, /*requestId*/Integer>>
90            mFrameNumberRequestPairs = new ArrayList<SimpleEntry<Long, Integer>>();
91
92    /**
93     * An object tracking received frame numbers.
94     * Updated when receiving callbacks from ICameraDeviceCallbacks.
95     */
96    private final FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();
97
98    private CameraCaptureSessionImpl mCurrentSession;
99
100    // Runnables for all state transitions, except error, which needs the
101    // error code argument
102
103    private final Runnable mCallOnOpened = new Runnable() {
104        @Override
105        public void run() {
106            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
107                if (scopedLock == null) return; // Camera already closed
108
109                StateListener sessionListener = mSessionStateListener;
110                if (sessionListener != null) {
111                    sessionListener.onOpened(CameraDeviceImpl.this);
112                }
113                mDeviceListener.onOpened(CameraDeviceImpl.this);
114            }
115        }
116    };
117
118    private final Runnable mCallOnUnconfigured = new Runnable() {
119        @Override
120        public void run() {
121            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
122                if (scopedLock == null) return; // Camera already closed
123
124                StateListener sessionListener = mSessionStateListener;
125                if (sessionListener != null) {
126                    sessionListener.onUnconfigured(CameraDeviceImpl.this);
127                }
128                mDeviceListener.onUnconfigured(CameraDeviceImpl.this);
129            }
130        }
131    };
132
133    private final Runnable mCallOnActive = new Runnable() {
134        @Override
135        public void run() {
136            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
137                if (scopedLock == null) return; // Camera already closed
138
139                StateListener sessionListener = mSessionStateListener;
140                if (sessionListener != null) {
141                    sessionListener.onActive(CameraDeviceImpl.this);
142                }
143                mDeviceListener.onActive(CameraDeviceImpl.this);
144            }
145        }
146    };
147
148    private final Runnable mCallOnBusy = new Runnable() {
149        @Override
150        public void run() {
151            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
152                if (scopedLock == null) return; // Camera already closed
153
154                StateListener sessionListener = mSessionStateListener;
155                if (sessionListener != null) {
156                    sessionListener.onBusy(CameraDeviceImpl.this);
157                }
158                mDeviceListener.onBusy(CameraDeviceImpl.this);
159            }
160        }
161    };
162
163    private final Runnable mCallOnClosed = new Runnable() {
164        private boolean mClosedOnce = false;
165
166        @Override
167        public void run() {
168            if (mClosedOnce) {
169                throw new AssertionError("Don't post #onClosed more than once");
170            }
171
172            StateListener sessionListener = mSessionStateListener;
173            if (sessionListener != null) {
174                sessionListener.onClosed(CameraDeviceImpl.this);
175            }
176            mDeviceListener.onClosed(CameraDeviceImpl.this);
177            mClosedOnce = true;
178        }
179    };
180
181    private final Runnable mCallOnIdle = new Runnable() {
182        @Override
183        public void run() {
184            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
185                if (scopedLock == null) return; // Camera already closed
186
187                StateListener sessionListener = mSessionStateListener;
188                if (sessionListener != null) {
189                    sessionListener.onIdle(CameraDeviceImpl.this);
190                }
191                mDeviceListener.onIdle(CameraDeviceImpl.this);
192            }
193        }
194    };
195
196    private final Runnable mCallOnDisconnected = new Runnable() {
197        @Override
198        public void run() {
199            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
200                if (scopedLock == null) return; // Camera already closed
201
202                StateListener sessionListener = mSessionStateListener;
203                if (sessionListener != null) {
204                    sessionListener.onDisconnected(CameraDeviceImpl.this);
205                }
206                mDeviceListener.onDisconnected(CameraDeviceImpl.this);
207            }
208        }
209    };
210
211    public CameraDeviceImpl(String cameraId, StateListener listener, Handler handler,
212                        CameraCharacteristics characteristics) {
213        if (cameraId == null || listener == null || handler == null) {
214            throw new IllegalArgumentException("Null argument given");
215        }
216        mCameraId = cameraId;
217        mDeviceListener = listener;
218        mDeviceHandler = handler;
219        mCharacteristics = characteristics;
220        mCloseLock = new CloseableLock(/*name*/"CD-" + mCameraId);
221
222        final int MAX_TAG_LEN = 23;
223        String tag = String.format("CameraDevice-JV-%s", mCameraId);
224        if (tag.length() > MAX_TAG_LEN) {
225            tag = tag.substring(0, MAX_TAG_LEN);
226        }
227        TAG = tag;
228        DEBUG = Log.isLoggable(TAG, Log.DEBUG);
229    }
230
231    public CameraDeviceCallbacks getCallbacks() {
232        return mCallbacks;
233    }
234
235    public void setRemoteDevice(ICameraDeviceUser remoteDevice) {
236        try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
237        // TODO: Move from decorator to direct binder-mediated exceptions
238            // If setRemoteFailure already called, do nothing
239            if (mInError) return;
240
241            mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice);
242
243            mDeviceHandler.post(mCallOnOpened);
244            mDeviceHandler.post(mCallOnUnconfigured);
245        }
246    }
247
248    /**
249     * Call to indicate failed connection to a remote camera device.
250     *
251     * <p>This places the camera device in the error state and informs the listener.
252     * Use in place of setRemoteDevice() when startup fails.</p>
253     */
254    public void setRemoteFailure(final CameraRuntimeException failure) {
255        int failureCode = StateListener.ERROR_CAMERA_DEVICE;
256        boolean failureIsError = true;
257
258        switch (failure.getReason()) {
259            case CameraAccessException.CAMERA_IN_USE:
260                failureCode = StateListener.ERROR_CAMERA_IN_USE;
261                break;
262            case CameraAccessException.MAX_CAMERAS_IN_USE:
263                failureCode = StateListener.ERROR_MAX_CAMERAS_IN_USE;
264                break;
265            case CameraAccessException.CAMERA_DISABLED:
266                failureCode = StateListener.ERROR_CAMERA_DISABLED;
267                break;
268            case CameraAccessException.CAMERA_DISCONNECTED:
269                failureIsError = false;
270                break;
271            case CameraAccessException.CAMERA_ERROR:
272                failureCode = StateListener.ERROR_CAMERA_DEVICE;
273                break;
274            default:
275                Log.wtf(TAG, "Unknown failure in opening camera device: " + failure.getReason());
276                break;
277        }
278        final int code = failureCode;
279        final boolean isError = failureIsError;
280        try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
281            if (scopedLock == null) return; // Camera already closed, can't go to error state
282
283            mInError = true;
284            mDeviceHandler.post(new Runnable() {
285                @Override
286                public void run() {
287                    if (isError) {
288                        mDeviceListener.onError(CameraDeviceImpl.this, code);
289                    } else {
290                        mDeviceListener.onDisconnected(CameraDeviceImpl.this);
291                    }
292                }
293            });
294        }
295    }
296
297    @Override
298    public String getId() {
299        return mCameraId;
300    }
301
302    @Override
303    public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
304        // Treat a null input the same an empty list
305        if (outputs == null) {
306            outputs = new ArrayList<Surface>();
307        }
308        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
309            checkIfCameraClosedOrInError();
310
311            HashSet<Surface> addSet = new HashSet<Surface>(outputs);    // Streams to create
312            List<Integer> deleteList = new ArrayList<Integer>();        // Streams to delete
313
314            // Determine which streams need to be created, which to be deleted
315            for (int i = 0; i < mConfiguredOutputs.size(); ++i) {
316                int streamId = mConfiguredOutputs.keyAt(i);
317                Surface s = mConfiguredOutputs.valueAt(i);
318
319                if (!outputs.contains(s)) {
320                    deleteList.add(streamId);
321                } else {
322                    addSet.remove(s);  // Don't create a stream previously created
323                }
324            }
325
326            mDeviceHandler.post(mCallOnBusy);
327            stopRepeating();
328
329            try {
330                waitUntilIdle();
331
332                mRemoteDevice.beginConfigure();
333                // Delete all streams first (to free up HW resources)
334                for (Integer streamId : deleteList) {
335                    mRemoteDevice.deleteStream(streamId);
336                    mConfiguredOutputs.delete(streamId);
337                }
338
339                // Add all new streams
340                for (Surface s : addSet) {
341                    // TODO: remove width,height,format since we are ignoring
342                    // it.
343                    int streamId = mRemoteDevice.createStream(0, 0, 0, s);
344                    mConfiguredOutputs.put(streamId, s);
345                }
346
347                mRemoteDevice.endConfigure();
348            } catch (CameraRuntimeException e) {
349                if (e.getReason() == CAMERA_IN_USE) {
350                    throw new IllegalStateException("The camera is currently busy." +
351                            " You must wait until the previous operation completes.");
352                }
353
354                throw e.asChecked();
355            } catch (RemoteException e) {
356                // impossible
357                return;
358            }
359
360            if (outputs.size() > 0) {
361                mDeviceHandler.post(mCallOnIdle);
362            } else {
363                mDeviceHandler.post(mCallOnUnconfigured);
364            }
365        }
366    }
367
368    @Override
369    public void createCaptureSession(List<Surface> outputs,
370            CameraCaptureSession.StateListener listener, Handler handler)
371            throws CameraAccessException {
372        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
373            if (DEBUG) {
374                Log.d(TAG, "createCaptureSession");
375            }
376
377            checkIfCameraClosedOrInError();
378
379            // TODO: we must be in UNCONFIGURED mode to begin with, or using another session
380
381            // TODO: dont block for this
382            boolean configureSuccess = true;
383            CameraAccessException pendingException = null;
384            try {
385                configureOutputs(outputs); // and then block until IDLE
386            } catch (CameraAccessException e) {
387                configureSuccess = false;
388                pendingException = e;
389                if (DEBUG) {
390                    Log.v(TAG, "createCaptureSession - failed with exception ", e);
391                }
392            }
393
394            // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise.
395            CameraCaptureSessionImpl newSession =
396                    new CameraCaptureSessionImpl(outputs, listener, handler, this, mDeviceHandler,
397                            configureSuccess);
398
399            if (mCurrentSession != null) {
400                mCurrentSession.replaceSessionClose(newSession);
401            }
402
403            // TODO: wait until current session closes, then create the new session
404            mCurrentSession = newSession;
405
406            if (pendingException != null) {
407                throw pendingException;
408            }
409
410            mSessionStateListener = mCurrentSession.getDeviceStateListener();
411        }
412    }
413
414    @Override
415    public CaptureRequest.Builder createCaptureRequest(int templateType)
416            throws CameraAccessException {
417        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
418            checkIfCameraClosedOrInError();
419
420            CameraMetadataNative templatedRequest = new CameraMetadataNative();
421
422            try {
423                mRemoteDevice.createDefaultRequest(templateType, /*out*/templatedRequest);
424            } catch (CameraRuntimeException e) {
425                throw e.asChecked();
426            } catch (RemoteException e) {
427                // impossible
428                return null;
429            }
430
431            CaptureRequest.Builder builder =
432                    new CaptureRequest.Builder(templatedRequest);
433
434            return builder;
435        }
436    }
437
438    @Override
439    public int capture(CaptureRequest request, CaptureListener listener, Handler handler)
440            throws CameraAccessException {
441        if (DEBUG) {
442            Log.d(TAG, "calling capture");
443        }
444        List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
445        requestList.add(request);
446        return submitCaptureRequest(requestList, listener, handler, /*streaming*/false);
447    }
448
449    @Override
450    public int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
451            Handler handler) throws CameraAccessException {
452        if (requests == null || requests.isEmpty()) {
453            throw new IllegalArgumentException("At least one request must be given");
454        }
455        return submitCaptureRequest(requests, listener, handler, /*streaming*/false);
456    }
457
458    /**
459     * This method checks lastFrameNumber returned from ICameraDeviceUser methods for
460     * starting and stopping repeating request and flushing.
461     *
462     * <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never
463     * sent to HAL. Then onCaptureSequenceCompleted is immediately triggered.
464     * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber pair
465     * is added to the list mFrameNumberRequestPairs.</p>
466     *
467     * @param requestId the request ID of the current repeating request.
468     *
469     * @param lastFrameNumber last frame number returned from binder.
470     */
471    private void checkEarlyTriggerSequenceComplete(
472            final int requestId, final long lastFrameNumber) {
473        // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request
474        // was never sent to HAL. Should trigger onCaptureSequenceCompleted immediately.
475        if (lastFrameNumber == CaptureListener.NO_FRAMES_CAPTURED) {
476            final CaptureListenerHolder holder;
477            int index = mCaptureListenerMap.indexOfKey(requestId);
478            holder = (index >= 0) ? mCaptureListenerMap.valueAt(index) : null;
479            if (holder != null) {
480                mCaptureListenerMap.removeAt(index);
481                if (DEBUG) {
482                    Log.v(TAG, String.format(
483                            "remove holder for requestId %d, "
484                            + "because lastFrame is %d.",
485                            requestId, lastFrameNumber));
486                }
487            }
488
489            if (holder != null) {
490                if (DEBUG) {
491                    Log.v(TAG, "immediately trigger onCaptureSequenceCompleted because"
492                            + " request did not reach HAL");
493                }
494
495                Runnable resultDispatch = new Runnable() {
496                    @Override
497                    public void run() {
498                        if (!CameraDeviceImpl.this.isClosed()) {
499                            if (DEBUG) {
500                                Log.d(TAG, String.format(
501                                        "early trigger sequence complete for request %d",
502                                        requestId));
503                            }
504                            if (lastFrameNumber < Integer.MIN_VALUE
505                                    || lastFrameNumber > Integer.MAX_VALUE) {
506                                throw new AssertionError(lastFrameNumber + " cannot be cast to int");
507                            }
508                            holder.getListener().onCaptureSequenceCompleted(
509                                    CameraDeviceImpl.this,
510                                    requestId,
511                                    lastFrameNumber);
512                        }
513                    }
514                };
515                holder.getHandler().post(resultDispatch);
516            } else {
517                Log.w(TAG, String.format(
518                        "did not register listener to request %d",
519                        requestId));
520            }
521        } else {
522            mFrameNumberRequestPairs.add(
523                    new SimpleEntry<Long, Integer>(lastFrameNumber,
524                            requestId));
525        }
526    }
527
528    private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureListener listener,
529            Handler handler, boolean repeating) throws CameraAccessException {
530
531        // Need a valid handler, or current thread needs to have a looper, if
532        // listener is valid
533        if (listener != null) {
534            handler = checkHandler(handler);
535        }
536
537        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
538            checkIfCameraClosedOrInError();
539            int requestId;
540
541            if (repeating) {
542                stopRepeating();
543            }
544
545            LongParcelable lastFrameNumberRef = new LongParcelable();
546            try {
547                requestId = mRemoteDevice.submitRequestList(requestList, repeating,
548                        /*out*/lastFrameNumberRef);
549                if (DEBUG) {
550                    Log.v(TAG, "last frame number " + lastFrameNumberRef.getNumber());
551                }
552            } catch (CameraRuntimeException e) {
553                throw e.asChecked();
554            } catch (RemoteException e) {
555                // impossible
556                return -1;
557            }
558
559            if (listener != null) {
560                mCaptureListenerMap.put(requestId, new CaptureListenerHolder(listener,
561                        requestList, handler, repeating));
562            } else {
563                if (DEBUG) {
564                    Log.d(TAG, "Listen for request " + requestId + " is null");
565                }
566            }
567
568            long lastFrameNumber = lastFrameNumberRef.getNumber();
569
570            if (repeating) {
571                if (mRepeatingRequestId != REQUEST_ID_NONE) {
572                    checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
573                }
574                mRepeatingRequestId = requestId;
575            } else {
576                mFrameNumberRequestPairs.add(
577                        new SimpleEntry<Long, Integer>(lastFrameNumber, requestId));
578            }
579
580            if (mIdle) {
581                mDeviceHandler.post(mCallOnActive);
582            }
583            mIdle = false;
584
585            return requestId;
586        }
587    }
588
589    @Override
590    public int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
591            Handler handler) throws CameraAccessException {
592        List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
593        requestList.add(request);
594        return submitCaptureRequest(requestList, listener, handler, /*streaming*/true);
595    }
596
597    @Override
598    public int setRepeatingBurst(List<CaptureRequest> requests, CaptureListener listener,
599            Handler handler) throws CameraAccessException {
600        if (requests == null || requests.isEmpty()) {
601            throw new IllegalArgumentException("At least one request must be given");
602        }
603        return submitCaptureRequest(requests, listener, handler, /*streaming*/true);
604    }
605
606    @Override
607    public void stopRepeating() throws CameraAccessException {
608
609        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
610            checkIfCameraClosedOrInError();
611            if (mRepeatingRequestId != REQUEST_ID_NONE) {
612
613                int requestId = mRepeatingRequestId;
614                mRepeatingRequestId = REQUEST_ID_NONE;
615
616                // Queue for deletion after in-flight requests finish
617                if (mCaptureListenerMap.get(requestId) != null) {
618                    mRepeatingRequestIdDeletedList.add(requestId);
619                }
620
621                try {
622                    LongParcelable lastFrameNumberRef = new LongParcelable();
623                    mRemoteDevice.cancelRequest(requestId, /*out*/lastFrameNumberRef);
624                    long lastFrameNumber = lastFrameNumberRef.getNumber();
625
626                    checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber);
627
628                } catch (CameraRuntimeException e) {
629                    throw e.asChecked();
630                } catch (RemoteException e) {
631                    // impossible
632                    return;
633                }
634            }
635        }
636    }
637
638    private void waitUntilIdle() throws CameraAccessException {
639
640        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
641            checkIfCameraClosedOrInError();
642            if (mRepeatingRequestId != REQUEST_ID_NONE) {
643                throw new IllegalStateException("Active repeating request ongoing");
644            }
645
646            try {
647                mRemoteDevice.waitUntilIdle();
648            } catch (CameraRuntimeException e) {
649                throw e.asChecked();
650            } catch (RemoteException e) {
651                // impossible
652                return;
653            }
654
655            mRepeatingRequestId = REQUEST_ID_NONE;
656        }
657    }
658
659    @Override
660    public void flush() throws CameraAccessException {
661        try (ScopedLock scopedLock = mCloseLock.acquireExclusiveLock()) {
662            checkIfCameraClosedOrInError();
663
664            mDeviceHandler.post(mCallOnBusy);
665            try {
666                LongParcelable lastFrameNumberRef = new LongParcelable();
667                mRemoteDevice.flush(/*out*/lastFrameNumberRef);
668                if (mRepeatingRequestId != REQUEST_ID_NONE) {
669                    long lastFrameNumber = lastFrameNumberRef.getNumber();
670                    checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
671                    mRepeatingRequestId = REQUEST_ID_NONE;
672                }
673            } catch (CameraRuntimeException e) {
674                throw e.asChecked();
675            } catch (RemoteException e) {
676                // impossible
677                return;
678            }
679        }
680    }
681
682    @Override
683    public void close() {
684        mClosing = true;
685        // Acquire exclusive lock, close, release (idempotent)
686        mCloseLock.close();
687
688        /*
689         *  The rest of this is safe, since no other methods will be able to execute
690         *  (they will throw ISE instead; the callbacks will get dropped)
691         */
692        {
693            try {
694                if (mRemoteDevice != null) {
695                    mRemoteDevice.disconnect();
696                }
697            } catch (CameraRuntimeException e) {
698                Log.e(TAG, "Exception while closing: ", e.asChecked());
699            } catch (RemoteException e) {
700                // impossible
701            }
702
703            // Only want to fire the onClosed callback once;
704            // either a normal close where the remote device is valid
705            // or a close after a startup error (no remote device but in error state)
706            if (mRemoteDevice != null || mInError) {
707                mDeviceHandler.post(mCallOnClosed);
708            }
709
710            mRemoteDevice = null;
711            mInError = false;
712        }
713    }
714
715    @Override
716    protected void finalize() throws Throwable {
717        try {
718            close();
719        }
720        finally {
721            super.finalize();
722        }
723    }
724
725    static class CaptureListenerHolder {
726
727        private final boolean mRepeating;
728        private final CaptureListener mListener;
729        private final List<CaptureRequest> mRequestList;
730        private final Handler mHandler;
731
732        CaptureListenerHolder(CaptureListener listener, List<CaptureRequest> requestList,
733                Handler handler, boolean repeating) {
734            if (listener == null || handler == null) {
735                throw new UnsupportedOperationException(
736                    "Must have a valid handler and a valid listener");
737            }
738            mRepeating = repeating;
739            mHandler = handler;
740            mRequestList = new ArrayList<CaptureRequest>(requestList);
741            mListener = listener;
742        }
743
744        public boolean isRepeating() {
745            return mRepeating;
746        }
747
748        public CaptureListener getListener() {
749            return mListener;
750        }
751
752        public CaptureRequest getRequest(int subsequenceId) {
753            if (subsequenceId >= mRequestList.size()) {
754                throw new IllegalArgumentException(
755                        String.format(
756                                "Requested subsequenceId %d is larger than request list size %d.",
757                                subsequenceId, mRequestList.size()));
758            } else {
759                if (subsequenceId < 0) {
760                    throw new IllegalArgumentException(String.format(
761                            "Requested subsequenceId %d is negative", subsequenceId));
762                } else {
763                    return mRequestList.get(subsequenceId);
764                }
765            }
766        }
767
768        public CaptureRequest getRequest() {
769            return getRequest(0);
770        }
771
772        public Handler getHandler() {
773            return mHandler;
774        }
775
776    }
777
778    /**
779     * This class tracks the last frame number for submitted requests.
780     */
781    public class FrameNumberTracker {
782
783        private long mCompletedFrameNumber = -1;
784        private final TreeSet<Long> mFutureErrorSet = new TreeSet<Long>();
785
786        private void update() {
787            Iterator<Long> iter = mFutureErrorSet.iterator();
788            while (iter.hasNext()) {
789                long errorFrameNumber = iter.next();
790                if (errorFrameNumber == mCompletedFrameNumber + 1) {
791                    mCompletedFrameNumber++;
792                    iter.remove();
793                } else {
794                    break;
795                }
796            }
797        }
798
799        /**
800         * This function is called every time when a result or an error is received.
801         * @param frameNumber: the frame number corresponding to the result or error
802         * @param isError: true if it is an error, false if it is not an error
803         */
804        public void updateTracker(long frameNumber, boolean isError) {
805            if (isError) {
806                mFutureErrorSet.add(frameNumber);
807            } else {
808                /**
809                 * HAL cannot send an OnResultReceived for frame N unless it knows for
810                 * sure that all frames prior to N have either errored out or completed.
811                 * So if the current frame is not an error, then all previous frames
812                 * should have arrived. The following line checks whether this holds.
813                 */
814                if (frameNumber != mCompletedFrameNumber + 1) {
815                    Log.e(TAG, String.format(
816                            "result frame number %d comes out of order, should be %d + 1",
817                            frameNumber, mCompletedFrameNumber));
818                }
819                mCompletedFrameNumber++;
820            }
821            update();
822        }
823
824        public long getCompletedFrameNumber() {
825            return mCompletedFrameNumber;
826        }
827
828    }
829
830    private void checkAndFireSequenceComplete() {
831        long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
832        Iterator<SimpleEntry<Long, Integer> > iter = mFrameNumberRequestPairs.iterator();
833        while (iter.hasNext()) {
834            final SimpleEntry<Long, Integer> frameNumberRequestPair = iter.next();
835            if (frameNumberRequestPair.getKey() <= completedFrameNumber) {
836
837                // remove request from mCaptureListenerMap
838                final int requestId = frameNumberRequestPair.getValue();
839                final CaptureListenerHolder holder;
840                try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
841                    if (scopedLock == null) {
842                        Log.w(TAG, "Camera closed while checking sequences");
843                        return;
844                    }
845
846                    int index = mCaptureListenerMap.indexOfKey(requestId);
847                    holder = (index >= 0) ? mCaptureListenerMap.valueAt(index)
848                            : null;
849                    if (holder != null) {
850                        mCaptureListenerMap.removeAt(index);
851                        if (DEBUG) {
852                            Log.v(TAG, String.format(
853                                    "remove holder for requestId %d, "
854                                    + "because lastFrame %d is <= %d",
855                                    requestId, frameNumberRequestPair.getKey(),
856                                    completedFrameNumber));
857                        }
858                    }
859                }
860                iter.remove();
861
862                // Call onCaptureSequenceCompleted
863                if (holder != null) {
864                    Runnable resultDispatch = new Runnable() {
865                        @Override
866                        public void run() {
867                            if (!CameraDeviceImpl.this.isClosed()){
868                                if (DEBUG) {
869                                    Log.d(TAG, String.format(
870                                            "fire sequence complete for request %d",
871                                            requestId));
872                                }
873
874                                long lastFrameNumber = frameNumberRequestPair.getKey();
875                                if (lastFrameNumber < Integer.MIN_VALUE
876                                        || lastFrameNumber > Integer.MAX_VALUE) {
877                                    throw new AssertionError(lastFrameNumber
878                                            + " cannot be cast to int");
879                                }
880                                holder.getListener().onCaptureSequenceCompleted(
881                                    CameraDeviceImpl.this,
882                                    requestId,
883                                    lastFrameNumber);
884                            }
885                        }
886                    };
887                    holder.getHandler().post(resultDispatch);
888                }
889
890            }
891        }
892    }
893
894    public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
895
896        //
897        // Constants below need to be kept up-to-date with
898        // frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
899        //
900
901        //
902        // Error codes for onCameraError
903        //
904
905        /**
906         * Camera has been disconnected
907         */
908        static final int ERROR_CAMERA_DISCONNECTED = 0;
909
910        /**
911         * Camera has encountered a device-level error
912         * Matches CameraDevice.StateListener#ERROR_CAMERA_DEVICE
913         */
914        static final int ERROR_CAMERA_DEVICE = 1;
915
916        /**
917         * Camera has encountered a service-level error
918         * Matches CameraDevice.StateListener#ERROR_CAMERA_SERVICE
919         */
920        static final int ERROR_CAMERA_SERVICE = 2;
921
922        @Override
923        public IBinder asBinder() {
924            return this;
925        }
926
927        @Override
928        public void onCameraError(final int errorCode, CaptureResultExtras resultExtras) {
929            Runnable r = null;
930
931            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
932                if (scopedLock == null) {
933                    return; // Camera already closed
934                }
935
936                mInError = true;
937                switch (errorCode) {
938                    case ERROR_CAMERA_DISCONNECTED:
939                        r = mCallOnDisconnected;
940                        break;
941                    default:
942                        Log.e(TAG, "Unknown error from camera device: " + errorCode);
943                        // no break
944                    case ERROR_CAMERA_DEVICE:
945                    case ERROR_CAMERA_SERVICE:
946                        r = new Runnable() {
947                            @Override
948                            public void run() {
949                                if (!CameraDeviceImpl.this.isClosed()) {
950                                    mDeviceListener.onError(CameraDeviceImpl.this, errorCode);
951                                }
952                            }
953                        };
954                        break;
955                }
956                CameraDeviceImpl.this.mDeviceHandler.post(r);
957
958                // Fire onCaptureSequenceCompleted
959                if (DEBUG) {
960                    Log.v(TAG, String.format("got error frame %d", resultExtras.getFrameNumber()));
961                }
962                mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(), /*error*/true);
963                checkAndFireSequenceComplete();
964            }
965        }
966
967        @Override
968        public void onCameraIdle() {
969            if (DEBUG) {
970                Log.d(TAG, "Camera now idle");
971            }
972            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
973                if (scopedLock == null) return; // Camera already closed
974
975                if (!CameraDeviceImpl.this.mIdle) {
976                    CameraDeviceImpl.this.mDeviceHandler.post(mCallOnIdle);
977                }
978                CameraDeviceImpl.this.mIdle = true;
979            }
980        }
981
982        @Override
983        public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
984            int requestId = resultExtras.getRequestId();
985            if (DEBUG) {
986                Log.d(TAG, "Capture started for id " + requestId);
987            }
988            final CaptureListenerHolder holder;
989
990            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
991                if (scopedLock == null) return; // Camera already closed
992
993                // Get the listener for this frame ID, if there is one
994                holder = CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
995
996                if (holder == null) {
997                    return;
998                }
999
1000                if (isClosed()) return;
1001
1002                // Dispatch capture start notice
1003                holder.getHandler().post(
1004                    new Runnable() {
1005                        @Override
1006                        public void run() {
1007                            if (!CameraDeviceImpl.this.isClosed()) {
1008                                holder.getListener().onCaptureStarted(
1009                                    CameraDeviceImpl.this,
1010                                    holder.getRequest(resultExtras.getSubsequenceId()),
1011                                    timestamp);
1012                            }
1013                        }
1014                    });
1015
1016            }
1017        }
1018
1019        @Override
1020        public void onResultReceived(CameraMetadataNative result,
1021                CaptureResultExtras resultExtras) throws RemoteException {
1022
1023            int requestId = resultExtras.getRequestId();
1024            if (DEBUG) {
1025                Log.v(TAG, "Received result frame " + resultExtras.getFrameNumber() + " for id "
1026                        + requestId);
1027            }
1028
1029            try (ScopedLock scopedLock = mCloseLock.acquireLock()) {
1030                if (scopedLock == null) return; // Camera already closed
1031
1032                // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
1033                result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
1034                        getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
1035
1036                final CaptureListenerHolder holder =
1037                        CameraDeviceImpl.this.mCaptureListenerMap.get(requestId);
1038
1039                Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT);
1040                boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial);
1041
1042                // Update tracker (increment counter) when it's not a partial result.
1043                if (!quirkIsPartialResult) {
1044                    mFrameNumberTracker.updateTracker(resultExtras.getFrameNumber(),
1045                            /*error*/false);
1046                }
1047
1048                // Check if we have a listener for this
1049                if (holder == null) {
1050                    if (DEBUG) {
1051                        Log.d(TAG,
1052                                "holder is null, early return at frame "
1053                                        + resultExtras.getFrameNumber());
1054                    }
1055                    return;
1056                }
1057
1058                if (isClosed()) {
1059                    if (DEBUG) {
1060                        Log.d(TAG,
1061                                "camera is closed, early return at frame "
1062                                        + resultExtras.getFrameNumber());
1063                    }
1064                    return;
1065                }
1066
1067                final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
1068
1069
1070                Runnable resultDispatch = null;
1071
1072                // Either send a partial result or the final capture completed result
1073                if (quirkIsPartialResult) {
1074                    final CaptureResult resultAsCapture =
1075                            new CaptureResult(result, request, requestId);
1076
1077                    // Partial result
1078                    resultDispatch = new Runnable() {
1079                        @Override
1080                        public void run() {
1081                            if (!CameraDeviceImpl.this.isClosed()){
1082                                holder.getListener().onCapturePartial(
1083                                    CameraDeviceImpl.this,
1084                                    request,
1085                                    resultAsCapture);
1086                            }
1087                        }
1088                    };
1089                } else {
1090                    final TotalCaptureResult resultAsCapture =
1091                            new TotalCaptureResult(result, request, requestId);
1092
1093                    // Final capture result
1094                    resultDispatch = new Runnable() {
1095                        @Override
1096                        public void run() {
1097                            if (!CameraDeviceImpl.this.isClosed()){
1098                                holder.getListener().onCaptureCompleted(
1099                                    CameraDeviceImpl.this,
1100                                    request,
1101                                    resultAsCapture);
1102                            }
1103                        }
1104                    };
1105                }
1106
1107                holder.getHandler().post(resultDispatch);
1108
1109                // Fire onCaptureSequenceCompleted
1110                if (!quirkIsPartialResult) {
1111                    checkAndFireSequenceComplete();
1112                }
1113
1114            }
1115        }
1116
1117    }
1118
1119    /**
1120     * Default handler management.
1121     *
1122     * <p>
1123     * If handler is null, get the current thread's
1124     * Looper to create a Handler with. If no looper exists, throw {@code IllegalArgumentException}.
1125     * </p>
1126     */
1127    static Handler checkHandler(Handler handler) {
1128        if (handler == null) {
1129            Looper looper = Looper.myLooper();
1130            if (looper == null) {
1131                throw new IllegalArgumentException(
1132                    "No handler given, and current thread has no looper!");
1133            }
1134            handler = new Handler(looper);
1135        }
1136        return handler;
1137    }
1138
1139    private void checkIfCameraClosedOrInError() throws CameraAccessException {
1140        if (mInError) {
1141            throw new CameraAccessException(CameraAccessException.CAMERA_ERROR,
1142                    "The camera device has encountered a serious error");
1143        }
1144        if (mRemoteDevice == null) {
1145            throw new IllegalStateException("CameraDevice was already closed");
1146        }
1147    }
1148
1149    /** Whether the camera device has started to close (may not yet have finished) */
1150    private boolean isClosed() {
1151        return mClosing;
1152    }
1153
1154    private CameraCharacteristics getCharacteristics() {
1155        return mCharacteristics;
1156    }
1157}
1158