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