[go: nahoru, domu]

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.graphics.ImageFormat;
22import android.hardware.camera2.CameraAccessException;
23import android.hardware.camera2.CameraCaptureSession;
24import android.hardware.camera2.CameraCharacteristics;
25import android.hardware.camera2.CameraDevice;
26import android.hardware.camera2.CaptureRequest;
27import android.hardware.camera2.CaptureResult;
28import android.hardware.camera2.CaptureFailure;
29import android.hardware.camera2.ICameraDeviceCallbacks;
30import android.hardware.camera2.ICameraDeviceUser;
31import android.hardware.camera2.TotalCaptureResult;
32import android.hardware.camera2.params.InputConfiguration;
33import android.hardware.camera2.params.OutputConfiguration;
34import android.hardware.camera2.params.ReprocessFormatsMap;
35import android.hardware.camera2.params.StreamConfigurationMap;
36import android.hardware.camera2.utils.SubmitInfo;
37import android.hardware.camera2.utils.SurfaceUtils;
38import android.hardware.ICameraService;
39import android.os.Handler;
40import android.os.IBinder;
41import android.os.Looper;
42import android.os.RemoteException;
43import android.os.ServiceSpecificException;
44import android.util.Log;
45import android.util.Range;
46import android.util.Size;
47import android.util.SparseArray;
48import android.view.Surface;
49
50import java.util.AbstractMap.SimpleEntry;
51import java.util.ArrayList;
52import java.util.Arrays;
53import java.util.Collection;
54import java.util.Collections;
55import java.util.concurrent.atomic.AtomicBoolean;
56import java.util.HashMap;
57import java.util.HashSet;
58import java.util.Iterator;
59import java.util.List;
60import java.util.LinkedList;
61import java.util.TreeMap;
62
63/**
64 * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate
65 */
66public class CameraDeviceImpl extends CameraDevice
67        implements IBinder.DeathRecipient {
68    private final String TAG;
69    private final boolean DEBUG = false;
70
71    private static final int REQUEST_ID_NONE = -1;
72
73    // TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
74    private ICameraDeviceUserWrapper mRemoteDevice;
75
76    // Lock to synchronize cross-thread access to device public interface
77    final Object mInterfaceLock = new Object(); // access from this class and Session only!
78    private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
79
80    private final StateCallback mDeviceCallback;
81    private volatile StateCallbackKK mSessionStateCallback;
82    private final Handler mDeviceHandler;
83
84    private final AtomicBoolean mClosing = new AtomicBoolean();
85    private boolean mInError = false;
86    private boolean mIdle = true;
87
88    /** map request IDs to callback/request data */
89    private final SparseArray<CaptureCallbackHolder> mCaptureCallbackMap =
90            new SparseArray<CaptureCallbackHolder>();
91
92    private int mRepeatingRequestId = REQUEST_ID_NONE;
93    // Map stream IDs to input/output configurations
94    private SimpleEntry<Integer, InputConfiguration> mConfiguredInput =
95            new SimpleEntry<>(REQUEST_ID_NONE, null);
96    private final SparseArray<OutputConfiguration> mConfiguredOutputs =
97            new SparseArray<>();
98
99    private final String mCameraId;
100    private final CameraCharacteristics mCharacteristics;
101    private final int mTotalPartialCount;
102
103    /**
104     * A list tracking request and its expected last regular frame number and last reprocess frame
105     * number. Updated when calling ICameraDeviceUser methods.
106     */
107    private final List<RequestLastFrameNumbersHolder> mRequestLastFrameNumbersList =
108            new ArrayList<>();
109
110    /**
111     * An object tracking received frame numbers.
112     * Updated when receiving callbacks from ICameraDeviceCallbacks.
113     */
114    private final FrameNumberTracker mFrameNumberTracker = new FrameNumberTracker();
115
116    private CameraCaptureSessionCore mCurrentSession;
117    private int mNextSessionId = 0;
118
119    // Runnables for all state transitions, except error, which needs the
120    // error code argument
121
122    private final Runnable mCallOnOpened = new Runnable() {
123        @Override
124        public void run() {
125            StateCallbackKK sessionCallback = null;
126            synchronized(mInterfaceLock) {
127                if (mRemoteDevice == null) return; // Camera already closed
128
129                sessionCallback = mSessionStateCallback;
130            }
131            if (sessionCallback != null) {
132                sessionCallback.onOpened(CameraDeviceImpl.this);
133            }
134            mDeviceCallback.onOpened(CameraDeviceImpl.this);
135        }
136    };
137
138    private final Runnable mCallOnUnconfigured = new Runnable() {
139        @Override
140        public void run() {
141            StateCallbackKK sessionCallback = null;
142            synchronized(mInterfaceLock) {
143                if (mRemoteDevice == null) return; // Camera already closed
144
145                sessionCallback = mSessionStateCallback;
146            }
147            if (sessionCallback != null) {
148                sessionCallback.onUnconfigured(CameraDeviceImpl.this);
149            }
150        }
151    };
152
153    private final Runnable mCallOnActive = new Runnable() {
154        @Override
155        public void run() {
156            StateCallbackKK sessionCallback = null;
157            synchronized(mInterfaceLock) {
158                if (mRemoteDevice == null) return; // Camera already closed
159
160                sessionCallback = mSessionStateCallback;
161            }
162            if (sessionCallback != null) {
163                sessionCallback.onActive(CameraDeviceImpl.this);
164            }
165        }
166    };
167
168    private final Runnable mCallOnBusy = new Runnable() {
169        @Override
170        public void run() {
171            StateCallbackKK sessionCallback = null;
172            synchronized(mInterfaceLock) {
173                if (mRemoteDevice == null) return; // Camera already closed
174
175                sessionCallback = mSessionStateCallback;
176            }
177            if (sessionCallback != null) {
178                sessionCallback.onBusy(CameraDeviceImpl.this);
179            }
180        }
181    };
182
183    private final Runnable mCallOnClosed = new Runnable() {
184        private boolean mClosedOnce = false;
185
186        @Override
187        public void run() {
188            if (mClosedOnce) {
189                throw new AssertionError("Don't post #onClosed more than once");
190            }
191            StateCallbackKK sessionCallback = null;
192            synchronized(mInterfaceLock) {
193                sessionCallback = mSessionStateCallback;
194            }
195            if (sessionCallback != null) {
196                sessionCallback.onClosed(CameraDeviceImpl.this);
197            }
198            mDeviceCallback.onClosed(CameraDeviceImpl.this);
199            mClosedOnce = true;
200        }
201    };
202
203    private final Runnable mCallOnIdle = new Runnable() {
204        @Override
205        public void run() {
206            StateCallbackKK sessionCallback = null;
207            synchronized(mInterfaceLock) {
208                if (mRemoteDevice == null) return; // Camera already closed
209
210                sessionCallback = mSessionStateCallback;
211            }
212            if (sessionCallback != null) {
213                sessionCallback.onIdle(CameraDeviceImpl.this);
214            }
215        }
216    };
217
218    private final Runnable mCallOnDisconnected = new Runnable() {
219        @Override
220        public void run() {
221            StateCallbackKK sessionCallback = null;
222            synchronized(mInterfaceLock) {
223                if (mRemoteDevice == null) return; // Camera already closed
224
225                sessionCallback = mSessionStateCallback;
226            }
227            if (sessionCallback != null) {
228                sessionCallback.onDisconnected(CameraDeviceImpl.this);
229            }
230            mDeviceCallback.onDisconnected(CameraDeviceImpl.this);
231        }
232    };
233
234    public CameraDeviceImpl(String cameraId, StateCallback callback, Handler handler,
235                        CameraCharacteristics characteristics) {
236        if (cameraId == null || callback == null || handler == null || characteristics == null) {
237            throw new IllegalArgumentException("Null argument given");
238        }
239        mCameraId = cameraId;
240        mDeviceCallback = callback;
241        mDeviceHandler = handler;
242        mCharacteristics = characteristics;
243
244        final int MAX_TAG_LEN = 23;
245        String tag = String.format("CameraDevice-JV-%s", mCameraId);
246        if (tag.length() > MAX_TAG_LEN) {
247            tag = tag.substring(0, MAX_TAG_LEN);
248        }
249        TAG = tag;
250
251        Integer partialCount =
252                mCharacteristics.get(CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT);
253        if (partialCount == null) {
254            // 1 means partial result is not supported.
255            mTotalPartialCount = 1;
256        } else {
257            mTotalPartialCount = partialCount;
258        }
259    }
260
261    public CameraDeviceCallbacks getCallbacks() {
262        return mCallbacks;
263    }
264
265    /**
266     * Set remote device, which triggers initial onOpened/onUnconfigured callbacks
267     *
268     * <p>This function may post onDisconnected and throw CAMERA_DISCONNECTED if remoteDevice dies
269     * during setup.</p>
270     *
271     */
272    public void setRemoteDevice(ICameraDeviceUser remoteDevice) throws CameraAccessException {
273        synchronized(mInterfaceLock) {
274            // TODO: Move from decorator to direct binder-mediated exceptions
275            // If setRemoteFailure already called, do nothing
276            if (mInError) return;
277
278            mRemoteDevice = new ICameraDeviceUserWrapper(remoteDevice);
279
280            IBinder remoteDeviceBinder = remoteDevice.asBinder();
281            // For legacy camera device, remoteDevice is in the same process, and
282            // asBinder returns NULL.
283            if (remoteDeviceBinder != null) {
284                try {
285                    remoteDeviceBinder.linkToDeath(this, /*flag*/ 0);
286                } catch (RemoteException e) {
287                    CameraDeviceImpl.this.mDeviceHandler.post(mCallOnDisconnected);
288
289                    throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
290                            "The camera device has encountered a serious error");
291                }
292            }
293
294            mDeviceHandler.post(mCallOnOpened);
295            mDeviceHandler.post(mCallOnUnconfigured);
296        }
297    }
298
299    /**
300     * Call to indicate failed connection to a remote camera device.
301     *
302     * <p>This places the camera device in the error state and informs the callback.
303     * Use in place of setRemoteDevice() when startup fails.</p>
304     */
305    public void setRemoteFailure(final ServiceSpecificException failure) {
306        int failureCode = StateCallback.ERROR_CAMERA_DEVICE;
307        boolean failureIsError = true;
308
309        switch (failure.errorCode) {
310            case ICameraService.ERROR_CAMERA_IN_USE:
311                failureCode = StateCallback.ERROR_CAMERA_IN_USE;
312                break;
313            case ICameraService.ERROR_MAX_CAMERAS_IN_USE:
314                failureCode = StateCallback.ERROR_MAX_CAMERAS_IN_USE;
315                break;
316            case ICameraService.ERROR_DISABLED:
317                failureCode = StateCallback.ERROR_CAMERA_DISABLED;
318                break;
319            case ICameraService.ERROR_DISCONNECTED:
320                failureIsError = false;
321                break;
322            case ICameraService.ERROR_INVALID_OPERATION:
323                failureCode = StateCallback.ERROR_CAMERA_DEVICE;
324                break;
325            default:
326                Log.e(TAG, "Unexpected failure in opening camera device: " + failure.errorCode +
327                        failure.getMessage());
328                break;
329        }
330        final int code = failureCode;
331        final boolean isError = failureIsError;
332        synchronized(mInterfaceLock) {
333            mInError = true;
334            mDeviceHandler.post(new Runnable() {
335                @Override
336                public void run() {
337                    if (isError) {
338                        mDeviceCallback.onError(CameraDeviceImpl.this, code);
339                    } else {
340                        mDeviceCallback.onDisconnected(CameraDeviceImpl.this);
341                    }
342                }
343            });
344        }
345    }
346
347    @Override
348    public String getId() {
349        return mCameraId;
350    }
351
352    public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
353        // Leave this here for backwards compatibility with older code using this directly
354        ArrayList<OutputConfiguration> outputConfigs = new ArrayList<>(outputs.size());
355        for (Surface s : outputs) {
356            outputConfigs.add(new OutputConfiguration(s));
357        }
358        configureStreamsChecked(/*inputConfig*/null, outputConfigs,
359                /*isConstrainedHighSpeed*/false);
360
361    }
362
363    /**
364     * Attempt to configure the input and outputs; the device goes to idle and then configures the
365     * new input and outputs if possible.
366     *
367     * <p>The configuration may gracefully fail, if input configuration is not supported,
368     * if there are too many outputs, if the formats are not supported, or if the sizes for that
369     * format is not supported. In this case this function will return {@code false} and the
370     * unconfigured callback will be fired.</p>
371     *
372     * <p>If the configuration succeeds (with 1 or more outputs with or without an input),
373     * then the idle callback is fired. Unconfiguring the device always fires the idle callback.</p>
374     *
375     * @param inputConfig input configuration or {@code null} for no input
376     * @param outputs a list of one or more surfaces, or {@code null} to unconfigure
377     * @param isConstrainedHighSpeed If the streams configuration is for constrained high speed output.
378     * @return whether or not the configuration was successful
379     *
380     * @throws CameraAccessException if there were any unexpected problems during configuration
381     */
382    public boolean configureStreamsChecked(InputConfiguration inputConfig,
383            List<OutputConfiguration> outputs, boolean isConstrainedHighSpeed)
384                    throws CameraAccessException {
385        // Treat a null input the same an empty list
386        if (outputs == null) {
387            outputs = new ArrayList<OutputConfiguration>();
388        }
389        if (outputs.size() == 0 && inputConfig != null) {
390            throw new IllegalArgumentException("cannot configure an input stream without " +
391                    "any output streams");
392        }
393
394        checkInputConfiguration(inputConfig);
395
396        boolean success = false;
397
398        synchronized(mInterfaceLock) {
399            checkIfCameraClosedOrInError();
400            // Streams to create
401            HashSet<OutputConfiguration> addSet = new HashSet<OutputConfiguration>(outputs);
402            // Streams to delete
403            List<Integer> deleteList = new ArrayList<Integer>();
404
405            // Determine which streams need to be created, which to be deleted
406            for (int i = 0; i < mConfiguredOutputs.size(); ++i) {
407                int streamId = mConfiguredOutputs.keyAt(i);
408                OutputConfiguration outConfig = mConfiguredOutputs.valueAt(i);
409
410                if (!outputs.contains(outConfig)) {
411                    deleteList.add(streamId);
412                } else {
413                    addSet.remove(outConfig);  // Don't create a stream previously created
414                }
415            }
416
417            mDeviceHandler.post(mCallOnBusy);
418            stopRepeating();
419
420            try {
421                waitUntilIdle();
422
423                mRemoteDevice.beginConfigure();
424
425                // reconfigure the input stream if the input configuration is different.
426                InputConfiguration currentInputConfig = mConfiguredInput.getValue();
427                if (inputConfig != currentInputConfig &&
428                        (inputConfig == null || !inputConfig.equals(currentInputConfig))) {
429                    if (currentInputConfig != null) {
430                        mRemoteDevice.deleteStream(mConfiguredInput.getKey());
431                        mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(
432                                REQUEST_ID_NONE, null);
433                    }
434                    if (inputConfig != null) {
435                        int streamId = mRemoteDevice.createInputStream(inputConfig.getWidth(),
436                                inputConfig.getHeight(), inputConfig.getFormat());
437                        mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(
438                                streamId, inputConfig);
439                    }
440                }
441
442                // Delete all streams first (to free up HW resources)
443                for (Integer streamId : deleteList) {
444                    mRemoteDevice.deleteStream(streamId);
445                    mConfiguredOutputs.delete(streamId);
446                }
447
448                // Add all new streams
449                for (OutputConfiguration outConfig : outputs) {
450                    if (addSet.contains(outConfig)) {
451                        int streamId = mRemoteDevice.createStream(outConfig);
452                        mConfiguredOutputs.put(streamId, outConfig);
453                    }
454                }
455
456                mRemoteDevice.endConfigure(isConstrainedHighSpeed);
457
458                success = true;
459            } catch (IllegalArgumentException e) {
460                // OK. camera service can reject stream config if it's not supported by HAL
461                // This is only the result of a programmer misusing the camera2 api.
462                Log.w(TAG, "Stream configuration failed due to: " + e.getMessage());
463                return false;
464            } catch (CameraAccessException e) {
465                if (e.getReason() == CameraAccessException.CAMERA_IN_USE) {
466                    throw new IllegalStateException("The camera is currently busy." +
467                            " You must wait until the previous operation completes.", e);
468                }
469                throw e;
470            } finally {
471                if (success && outputs.size() > 0) {
472                    mDeviceHandler.post(mCallOnIdle);
473                } else {
474                    // Always return to the 'unconfigured' state if we didn't hit a fatal error
475                    mDeviceHandler.post(mCallOnUnconfigured);
476                }
477            }
478        }
479
480        return success;
481    }
482
483    @Override
484    public void createCaptureSession(List<Surface> outputs,
485            CameraCaptureSession.StateCallback callback, Handler handler)
486            throws CameraAccessException {
487        List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
488        for (Surface surface : outputs) {
489            outConfigurations.add(new OutputConfiguration(surface));
490        }
491        createCaptureSessionInternal(null, outConfigurations, callback, handler,
492                /*isConstrainedHighSpeed*/false);
493    }
494
495    @Override
496    public void createCaptureSessionByOutputConfigurations(
497            List<OutputConfiguration> outputConfigurations,
498            CameraCaptureSession.StateCallback callback, Handler handler)
499            throws CameraAccessException {
500        if (DEBUG) {
501            Log.d(TAG, "createCaptureSessionByOutputConfiguration");
502        }
503
504        // OutputConfiguration objects are immutable, but need to have our own array
505        List<OutputConfiguration> currentOutputs = new ArrayList<>(outputConfigurations);
506
507        createCaptureSessionInternal(null, currentOutputs, callback, handler,
508                /*isConstrainedHighSpeed*/false);
509    }
510
511    @Override
512    public void createReprocessableCaptureSession(InputConfiguration inputConfig,
513            List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler)
514            throws CameraAccessException {
515        if (DEBUG) {
516            Log.d(TAG, "createReprocessableCaptureSession");
517        }
518
519        if (inputConfig == null) {
520            throw new IllegalArgumentException("inputConfig cannot be null when creating a " +
521                    "reprocessable capture session");
522        }
523        List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
524        for (Surface surface : outputs) {
525            outConfigurations.add(new OutputConfiguration(surface));
526        }
527        createCaptureSessionInternal(inputConfig, outConfigurations, callback, handler,
528                /*isConstrainedHighSpeed*/false);
529    }
530
531    @Override
532    public void createReprocessableCaptureSessionByConfigurations(InputConfiguration inputConfig,
533            List<OutputConfiguration> outputs,
534            android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler)
535                    throws CameraAccessException {
536        if (DEBUG) {
537            Log.d(TAG, "createReprocessableCaptureSessionWithConfigurations");
538        }
539
540        if (inputConfig == null) {
541            throw new IllegalArgumentException("inputConfig cannot be null when creating a " +
542                    "reprocessable capture session");
543        }
544
545        if (outputs == null) {
546            throw new IllegalArgumentException("Output configurations cannot be null when " +
547                    "creating a reprocessable capture session");
548        }
549
550        // OutputConfiguration objects aren't immutable, make a copy before using.
551        List<OutputConfiguration> currentOutputs = new ArrayList<OutputConfiguration>();
552        for (OutputConfiguration output : outputs) {
553            currentOutputs.add(new OutputConfiguration(output));
554        }
555        createCaptureSessionInternal(inputConfig, currentOutputs,
556                callback, handler, /*isConstrainedHighSpeed*/false);
557    }
558
559    @Override
560    public void createConstrainedHighSpeedCaptureSession(List<Surface> outputs,
561            android.hardware.camera2.CameraCaptureSession.StateCallback callback, Handler handler)
562            throws CameraAccessException {
563        if (outputs == null || outputs.size() == 0 || outputs.size() > 2) {
564            throw new IllegalArgumentException(
565                    "Output surface list must not be null and the size must be no more than 2");
566        }
567        StreamConfigurationMap config =
568                getCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
569        SurfaceUtils.checkConstrainedHighSpeedSurfaces(outputs, /*fpsRange*/null, config);
570
571        List<OutputConfiguration> outConfigurations = new ArrayList<>(outputs.size());
572        for (Surface surface : outputs) {
573            outConfigurations.add(new OutputConfiguration(surface));
574        }
575        createCaptureSessionInternal(null, outConfigurations, callback, handler,
576                /*isConstrainedHighSpeed*/true);
577    }
578
579    private void createCaptureSessionInternal(InputConfiguration inputConfig,
580            List<OutputConfiguration> outputConfigurations,
581            CameraCaptureSession.StateCallback callback, Handler handler,
582            boolean isConstrainedHighSpeed) throws CameraAccessException {
583        synchronized(mInterfaceLock) {
584            if (DEBUG) {
585                Log.d(TAG, "createCaptureSessionInternal");
586            }
587
588            checkIfCameraClosedOrInError();
589
590            if (isConstrainedHighSpeed && inputConfig != null) {
591                throw new IllegalArgumentException("Constrained high speed session doesn't support"
592                        + " input configuration yet.");
593            }
594
595            // Notify current session that it's going away, before starting camera operations
596            // After this call completes, the session is not allowed to call into CameraDeviceImpl
597            if (mCurrentSession != null) {
598                mCurrentSession.replaceSessionClose();
599            }
600
601            // TODO: dont block for this
602            boolean configureSuccess = true;
603            CameraAccessException pendingException = null;
604            Surface input = null;
605            try {
606                // configure streams and then block until IDLE
607                configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations,
608                        isConstrainedHighSpeed);
609                if (configureSuccess == true && inputConfig != null) {
610                    input = mRemoteDevice.getInputSurface();
611                }
612            } catch (CameraAccessException e) {
613                configureSuccess = false;
614                pendingException = e;
615                input = null;
616                if (DEBUG) {
617                    Log.v(TAG, "createCaptureSession - failed with exception ", e);
618                }
619            }
620
621            List<Surface> outSurfaces = new ArrayList<>(outputConfigurations.size());
622            for (OutputConfiguration config : outputConfigurations) {
623                outSurfaces.add(config.getSurface());
624            }
625            // Fire onConfigured if configureOutputs succeeded, fire onConfigureFailed otherwise.
626            CameraCaptureSessionCore newSession = null;
627            if (isConstrainedHighSpeed) {
628                newSession = new CameraConstrainedHighSpeedCaptureSessionImpl(mNextSessionId++,
629                        outSurfaces, callback, handler, this, mDeviceHandler, configureSuccess,
630                        mCharacteristics);
631            } else {
632                newSession = new CameraCaptureSessionImpl(mNextSessionId++, input,
633                        outSurfaces, callback, handler, this, mDeviceHandler,
634                        configureSuccess);
635            }
636
637            // TODO: wait until current session closes, then create the new session
638            mCurrentSession = newSession;
639
640            if (pendingException != null) {
641                throw pendingException;
642            }
643
644            mSessionStateCallback = mCurrentSession.getDeviceStateCallback();
645        }
646    }
647
648    /**
649     * For use by backwards-compatibility code only.
650     */
651    public void setSessionListener(StateCallbackKK sessionCallback) {
652        synchronized(mInterfaceLock) {
653            mSessionStateCallback = sessionCallback;
654        }
655    }
656
657    @Override
658    public CaptureRequest.Builder createCaptureRequest(int templateType)
659            throws CameraAccessException {
660        synchronized(mInterfaceLock) {
661            checkIfCameraClosedOrInError();
662
663            CameraMetadataNative templatedRequest = null;
664
665            templatedRequest = mRemoteDevice.createDefaultRequest(templateType);
666
667            CaptureRequest.Builder builder = new CaptureRequest.Builder(
668                    templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE);
669
670            return builder;
671        }
672    }
673
674    @Override
675    public CaptureRequest.Builder createReprocessCaptureRequest(TotalCaptureResult inputResult)
676            throws CameraAccessException {
677        synchronized(mInterfaceLock) {
678            checkIfCameraClosedOrInError();
679
680            CameraMetadataNative resultMetadata = new
681                    CameraMetadataNative(inputResult.getNativeCopy());
682
683            return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true,
684                    inputResult.getSessionId());
685        }
686    }
687
688    public void prepare(Surface surface) throws CameraAccessException {
689        if (surface == null) throw new IllegalArgumentException("Surface is null");
690
691        synchronized(mInterfaceLock) {
692            int streamId = -1;
693            for (int i = 0; i < mConfiguredOutputs.size(); i++) {
694                if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
695                    streamId = mConfiguredOutputs.keyAt(i);
696                    break;
697                }
698            }
699            if (streamId == -1) {
700                throw new IllegalArgumentException("Surface is not part of this session");
701            }
702
703            mRemoteDevice.prepare(streamId);
704        }
705    }
706
707    public void prepare(int maxCount, Surface surface) throws CameraAccessException {
708        if (surface == null) throw new IllegalArgumentException("Surface is null");
709        if (maxCount <= 0) throw new IllegalArgumentException("Invalid maxCount given: " +
710                maxCount);
711
712        synchronized(mInterfaceLock) {
713            int streamId = -1;
714            for (int i = 0; i < mConfiguredOutputs.size(); i++) {
715                if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
716                    streamId = mConfiguredOutputs.keyAt(i);
717                    break;
718                }
719            }
720            if (streamId == -1) {
721                throw new IllegalArgumentException("Surface is not part of this session");
722            }
723
724            mRemoteDevice.prepare2(maxCount, streamId);
725        }
726    }
727
728    public void tearDown(Surface surface) throws CameraAccessException {
729        if (surface == null) throw new IllegalArgumentException("Surface is null");
730
731        synchronized(mInterfaceLock) {
732            int streamId = -1;
733            for (int i = 0; i < mConfiguredOutputs.size(); i++) {
734                if (surface == mConfiguredOutputs.valueAt(i).getSurface()) {
735                    streamId = mConfiguredOutputs.keyAt(i);
736                    break;
737                }
738            }
739            if (streamId == -1) {
740                throw new IllegalArgumentException("Surface is not part of this session");
741            }
742
743            mRemoteDevice.tearDown(streamId);
744        }
745    }
746
747    public int capture(CaptureRequest request, CaptureCallback callback, Handler handler)
748            throws CameraAccessException {
749        if (DEBUG) {
750            Log.d(TAG, "calling capture");
751        }
752        List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
753        requestList.add(request);
754        return submitCaptureRequest(requestList, callback, handler, /*streaming*/false);
755    }
756
757    public int captureBurst(List<CaptureRequest> requests, CaptureCallback callback,
758            Handler handler) throws CameraAccessException {
759        if (requests == null || requests.isEmpty()) {
760            throw new IllegalArgumentException("At least one request must be given");
761        }
762        return submitCaptureRequest(requests, callback, handler, /*streaming*/false);
763    }
764
765    /**
766     * This method checks lastFrameNumber returned from ICameraDeviceUser methods for
767     * starting and stopping repeating request and flushing.
768     *
769     * <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never
770     * sent to HAL. Then onCaptureSequenceAborted is immediately triggered.
771     * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber as the last
772     * regular frame number will be added to the list mRequestLastFrameNumbersList.</p>
773     *
774     * @param requestId the request ID of the current repeating request.
775     *
776     * @param lastFrameNumber last frame number returned from binder.
777     */
778    private void checkEarlyTriggerSequenceComplete(
779            final int requestId, final long lastFrameNumber) {
780        // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request
781        // was never sent to HAL. Should trigger onCaptureSequenceAborted immediately.
782        if (lastFrameNumber == CaptureCallback.NO_FRAMES_CAPTURED) {
783            final CaptureCallbackHolder holder;
784            int index = mCaptureCallbackMap.indexOfKey(requestId);
785            holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) : null;
786            if (holder != null) {
787                mCaptureCallbackMap.removeAt(index);
788                if (DEBUG) {
789                    Log.v(TAG, String.format(
790                            "remove holder for requestId %d, "
791                            + "because lastFrame is %d.",
792                            requestId, lastFrameNumber));
793                }
794            }
795
796            if (holder != null) {
797                if (DEBUG) {
798                    Log.v(TAG, "immediately trigger onCaptureSequenceAborted because"
799                            + " request did not reach HAL");
800                }
801
802                Runnable resultDispatch = new Runnable() {
803                    @Override
804                    public void run() {
805                        if (!CameraDeviceImpl.this.isClosed()) {
806                            if (DEBUG) {
807                                Log.d(TAG, String.format(
808                                        "early trigger sequence complete for request %d",
809                                        requestId));
810                            }
811                            holder.getCallback().onCaptureSequenceAborted(
812                                    CameraDeviceImpl.this,
813                                    requestId);
814                        }
815                    }
816                };
817                holder.getHandler().post(resultDispatch);
818            } else {
819                Log.w(TAG, String.format(
820                        "did not register callback to request %d",
821                        requestId));
822            }
823        } else {
824            // This function is only called for regular request so lastFrameNumber is the last
825            // regular frame number.
826            mRequestLastFrameNumbersList.add(new RequestLastFrameNumbersHolder(requestId,
827                    lastFrameNumber));
828
829            // It is possible that the last frame has already arrived, so we need to check
830            // for sequence completion right away
831            checkAndFireSequenceComplete();
832        }
833    }
834
835    private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureCallback callback,
836            Handler handler, boolean repeating) throws CameraAccessException {
837
838        // Need a valid handler, or current thread needs to have a looper, if
839        // callback is valid
840        handler = checkHandler(handler, callback);
841
842        // Make sure that there all requests have at least 1 surface; all surfaces are non-null
843        for (CaptureRequest request : requestList) {
844            if (request.getTargets().isEmpty()) {
845                throw new IllegalArgumentException(
846                        "Each request must have at least one Surface target");
847            }
848
849            for (Surface surface : request.getTargets()) {
850                if (surface == null) {
851                    throw new IllegalArgumentException("Null Surface targets are not allowed");
852                }
853            }
854        }
855
856        synchronized(mInterfaceLock) {
857            checkIfCameraClosedOrInError();
858            if (repeating) {
859                stopRepeating();
860            }
861
862            SubmitInfo requestInfo;
863
864            CaptureRequest[] requestArray = requestList.toArray(new CaptureRequest[requestList.size()]);
865            requestInfo = mRemoteDevice.submitRequestList(requestArray, repeating);
866            if (DEBUG) {
867                Log.v(TAG, "last frame number " + requestInfo.getLastFrameNumber());
868            }
869
870            if (callback != null) {
871                mCaptureCallbackMap.put(requestInfo.getRequestId(),
872                        new CaptureCallbackHolder(
873                            callback, requestList, handler, repeating, mNextSessionId - 1));
874            } else {
875                if (DEBUG) {
876                    Log.d(TAG, "Listen for request " + requestInfo.getRequestId() + " is null");
877                }
878            }
879
880            if (repeating) {
881                if (mRepeatingRequestId != REQUEST_ID_NONE) {
882                    checkEarlyTriggerSequenceComplete(mRepeatingRequestId,
883                            requestInfo.getLastFrameNumber());
884                }
885                mRepeatingRequestId = requestInfo.getRequestId();
886            } else {
887                mRequestLastFrameNumbersList.add(
888                    new RequestLastFrameNumbersHolder(requestList, requestInfo));
889            }
890
891            if (mIdle) {
892                mDeviceHandler.post(mCallOnActive);
893            }
894            mIdle = false;
895
896            return requestInfo.getRequestId();
897        }
898    }
899
900    public int setRepeatingRequest(CaptureRequest request, CaptureCallback callback,
901            Handler handler) throws CameraAccessException {
902        List<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
903        requestList.add(request);
904        return submitCaptureRequest(requestList, callback, handler, /*streaming*/true);
905    }
906
907    public int setRepeatingBurst(List<CaptureRequest> requests, CaptureCallback callback,
908            Handler handler) throws CameraAccessException {
909        if (requests == null || requests.isEmpty()) {
910            throw new IllegalArgumentException("At least one request must be given");
911        }
912        return submitCaptureRequest(requests, callback, handler, /*streaming*/true);
913    }
914
915    public void stopRepeating() throws CameraAccessException {
916
917        synchronized(mInterfaceLock) {
918            checkIfCameraClosedOrInError();
919            if (mRepeatingRequestId != REQUEST_ID_NONE) {
920
921                int requestId = mRepeatingRequestId;
922                mRepeatingRequestId = REQUEST_ID_NONE;
923
924                long lastFrameNumber;
925                try {
926                    lastFrameNumber = mRemoteDevice.cancelRequest(requestId);
927                } catch (IllegalArgumentException e) {
928                    if (DEBUG) {
929                        Log.v(TAG, "Repeating request was already stopped for request " + requestId);
930                    }
931                    // Repeating request was already stopped. Nothing more to do.
932                    return;
933                }
934
935                checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber);
936            }
937        }
938    }
939
940    private void waitUntilIdle() throws CameraAccessException {
941
942        synchronized(mInterfaceLock) {
943            checkIfCameraClosedOrInError();
944
945            if (mRepeatingRequestId != REQUEST_ID_NONE) {
946                throw new IllegalStateException("Active repeating request ongoing");
947            }
948
949            mRemoteDevice.waitUntilIdle();
950        }
951    }
952
953    public void flush() throws CameraAccessException {
954        synchronized(mInterfaceLock) {
955            checkIfCameraClosedOrInError();
956
957            mDeviceHandler.post(mCallOnBusy);
958
959            // If already idle, just do a busy->idle transition immediately, don't actually
960            // flush.
961            if (mIdle) {
962                mDeviceHandler.post(mCallOnIdle);
963                return;
964            }
965
966            long lastFrameNumber = mRemoteDevice.flush();
967            if (mRepeatingRequestId != REQUEST_ID_NONE) {
968                checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
969                mRepeatingRequestId = REQUEST_ID_NONE;
970            }
971        }
972    }
973
974    @Override
975    public void close() {
976        synchronized (mInterfaceLock) {
977            if (mClosing.getAndSet(true)) {
978                return;
979            }
980
981            if (mRemoteDevice != null) {
982                mRemoteDevice.disconnect();
983                mRemoteDevice.unlinkToDeath(this, /*flags*/0);
984            }
985
986            // Only want to fire the onClosed callback once;
987            // either a normal close where the remote device is valid
988            // or a close after a startup error (no remote device but in error state)
989            if (mRemoteDevice != null || mInError) {
990                mDeviceHandler.post(mCallOnClosed);
991            }
992
993            mRemoteDevice = null;
994        }
995    }
996
997    @Override
998    protected void finalize() throws Throwable {
999        try {
1000            close();
1001        }
1002        finally {
1003            super.finalize();
1004        }
1005    }
1006
1007    private void checkInputConfiguration(InputConfiguration inputConfig) {
1008        if (inputConfig != null) {
1009            StreamConfigurationMap configMap = mCharacteristics.get(
1010                    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
1011
1012            int[] inputFormats = configMap.getInputFormats();
1013            boolean validFormat = false;
1014            for (int format : inputFormats) {
1015                if (format == inputConfig.getFormat()) {
1016                    validFormat = true;
1017                }
1018            }
1019
1020            if (validFormat == false) {
1021                throw new IllegalArgumentException("input format " + inputConfig.getFormat() +
1022                        " is not valid");
1023            }
1024
1025            boolean validSize = false;
1026            Size[] inputSizes = configMap.getInputSizes(inputConfig.getFormat());
1027            for (Size s : inputSizes) {
1028                if (inputConfig.getWidth() == s.getWidth() &&
1029                        inputConfig.getHeight() == s.getHeight()) {
1030                    validSize = true;
1031                }
1032            }
1033
1034            if (validSize == false) {
1035                throw new IllegalArgumentException("input size " + inputConfig.getWidth() + "x" +
1036                        inputConfig.getHeight() + " is not valid");
1037            }
1038        }
1039    }
1040
1041    /**
1042     * <p>A callback for tracking the progress of a {@link CaptureRequest}
1043     * submitted to the camera device.</p>
1044     *
1045     */
1046    public static abstract class CaptureCallback {
1047
1048        /**
1049         * This constant is used to indicate that no images were captured for
1050         * the request.
1051         *
1052         * @hide
1053         */
1054        public static final int NO_FRAMES_CAPTURED = -1;
1055
1056        /**
1057         * This method is called when the camera device has started capturing
1058         * the output image for the request, at the beginning of image exposure.
1059         *
1060         * @see android.media.MediaActionSound
1061         */
1062        public void onCaptureStarted(CameraDevice camera,
1063                CaptureRequest request, long timestamp, long frameNumber) {
1064            // default empty implementation
1065        }
1066
1067        /**
1068         * This method is called when some results from an image capture are
1069         * available.
1070         *
1071         * @hide
1072         */
1073        public void onCapturePartial(CameraDevice camera,
1074                CaptureRequest request, CaptureResult result) {
1075            // default empty implementation
1076        }
1077
1078        /**
1079         * This method is called when an image capture makes partial forward progress; some
1080         * (but not all) results from an image capture are available.
1081         *
1082         */
1083        public void onCaptureProgressed(CameraDevice camera,
1084                CaptureRequest request, CaptureResult partialResult) {
1085            // default empty implementation
1086        }
1087
1088        /**
1089         * This method is called when an image capture has fully completed and all the
1090         * result metadata is available.
1091         */
1092        public void onCaptureCompleted(CameraDevice camera,
1093                CaptureRequest request, TotalCaptureResult result) {
1094            // default empty implementation
1095        }
1096
1097        /**
1098         * This method is called instead of {@link #onCaptureCompleted} when the
1099         * camera device failed to produce a {@link CaptureResult} for the
1100         * request.
1101         */
1102        public void onCaptureFailed(CameraDevice camera,
1103                CaptureRequest request, CaptureFailure failure) {
1104            // default empty implementation
1105        }
1106
1107        /**
1108         * This method is called independently of the others in CaptureCallback,
1109         * when a capture sequence finishes and all {@link CaptureResult}
1110         * or {@link CaptureFailure} for it have been returned via this callback.
1111         */
1112        public void onCaptureSequenceCompleted(CameraDevice camera,
1113                int sequenceId, long frameNumber) {
1114            // default empty implementation
1115        }
1116
1117        /**
1118         * This method is called independently of the others in CaptureCallback,
1119         * when a capture sequence aborts before any {@link CaptureResult}
1120         * or {@link CaptureFailure} for it have been returned via this callback.
1121         */
1122        public void onCaptureSequenceAborted(CameraDevice camera,
1123                int sequenceId) {
1124            // default empty implementation
1125        }
1126
1127        public void onCaptureBufferLost(CameraDevice camera,
1128                CaptureRequest request, Surface target, long frameNumber) {
1129            // default empty implementation
1130        }
1131    }
1132
1133    /**
1134     * A callback for notifications about the state of a camera device, adding in the callbacks that
1135     * were part of the earlier KK API design, but now only used internally.
1136     */
1137    public static abstract class StateCallbackKK extends StateCallback {
1138        /**
1139         * The method called when a camera device has no outputs configured.
1140         *
1141         */
1142        public void onUnconfigured(CameraDevice camera) {
1143            // Default empty implementation
1144        }
1145
1146        /**
1147         * The method called when a camera device begins processing
1148         * {@link CaptureRequest capture requests}.
1149         *
1150         */
1151        public void onActive(CameraDevice camera) {
1152            // Default empty implementation
1153        }
1154
1155        /**
1156         * The method called when a camera device is busy.
1157         *
1158         */
1159        public void onBusy(CameraDevice camera) {
1160            // Default empty implementation
1161        }
1162
1163        /**
1164         * The method called when a camera device has finished processing all
1165         * submitted capture requests and has reached an idle state.
1166         *
1167         */
1168        public void onIdle(CameraDevice camera) {
1169            // Default empty implementation
1170        }
1171
1172        /**
1173         * The method called when the camera device has finished preparing
1174         * an output Surface
1175         */
1176        public void onSurfacePrepared(Surface surface) {
1177            // Default empty implementation
1178        }
1179    }
1180
1181    static class CaptureCallbackHolder {
1182
1183        private final boolean mRepeating;
1184        private final CaptureCallback mCallback;
1185        private final List<CaptureRequest> mRequestList;
1186        private final Handler mHandler;
1187        private final int mSessionId;
1188
1189        CaptureCallbackHolder(CaptureCallback callback, List<CaptureRequest> requestList,
1190                Handler handler, boolean repeating, int sessionId) {
1191            if (callback == null || handler == null) {
1192                throw new UnsupportedOperationException(
1193                    "Must have a valid handler and a valid callback");
1194            }
1195            mRepeating = repeating;
1196            mHandler = handler;
1197            mRequestList = new ArrayList<CaptureRequest>(requestList);
1198            mCallback = callback;
1199            mSessionId = sessionId;
1200        }
1201
1202        public boolean isRepeating() {
1203            return mRepeating;
1204        }
1205
1206        public CaptureCallback getCallback() {
1207            return mCallback;
1208        }
1209
1210        public CaptureRequest getRequest(int subsequenceId) {
1211            if (subsequenceId >= mRequestList.size()) {
1212                throw new IllegalArgumentException(
1213                        String.format(
1214                                "Requested subsequenceId %d is larger than request list size %d.",
1215                                subsequenceId, mRequestList.size()));
1216            } else {
1217                if (subsequenceId < 0) {
1218                    throw new IllegalArgumentException(String.format(
1219                            "Requested subsequenceId %d is negative", subsequenceId));
1220                } else {
1221                    return mRequestList.get(subsequenceId);
1222                }
1223            }
1224        }
1225
1226        public CaptureRequest getRequest() {
1227            return getRequest(0);
1228        }
1229
1230        public Handler getHandler() {
1231            return mHandler;
1232        }
1233
1234        public int getSessionId() {
1235            return mSessionId;
1236        }
1237    }
1238
1239    /**
1240     * This class holds a capture ID and its expected last regular frame number and last reprocess
1241     * frame number.
1242     */
1243    static class RequestLastFrameNumbersHolder {
1244        // request ID
1245        private final int mRequestId;
1246        // The last regular frame number for this request ID. It's
1247        // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no regular request.
1248        private final long mLastRegularFrameNumber;
1249        // The last reprocess frame number for this request ID. It's
1250        // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no reprocess request.
1251        private final long mLastReprocessFrameNumber;
1252
1253        /**
1254         * Create a request-last-frame-numbers holder with a list of requests, request ID, and
1255         * the last frame number returned by camera service.
1256         */
1257        public RequestLastFrameNumbersHolder(List<CaptureRequest> requestList, SubmitInfo requestInfo) {
1258            long lastRegularFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
1259            long lastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
1260            long frameNumber = requestInfo.getLastFrameNumber();
1261
1262            if (requestInfo.getLastFrameNumber() < requestList.size() - 1) {
1263                throw new IllegalArgumentException(
1264                        "lastFrameNumber: " + requestInfo.getLastFrameNumber() +
1265                        " should be at least " + (requestList.size() - 1) + " for the number of " +
1266                        " requests in the list: " + requestList.size());
1267            }
1268
1269            // find the last regular frame number and the last reprocess frame number
1270            for (int i = requestList.size() - 1; i >= 0; i--) {
1271                CaptureRequest request = requestList.get(i);
1272                if (request.isReprocess() && lastReprocessFrameNumber ==
1273                        CaptureCallback.NO_FRAMES_CAPTURED) {
1274                    lastReprocessFrameNumber = frameNumber;
1275                } else if (!request.isReprocess() && lastRegularFrameNumber ==
1276                        CaptureCallback.NO_FRAMES_CAPTURED) {
1277                    lastRegularFrameNumber = frameNumber;
1278                }
1279
1280                if (lastReprocessFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED &&
1281                        lastRegularFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED) {
1282                    break;
1283                }
1284
1285                frameNumber--;
1286            }
1287
1288            mLastRegularFrameNumber = lastRegularFrameNumber;
1289            mLastReprocessFrameNumber = lastReprocessFrameNumber;
1290            mRequestId = requestInfo.getRequestId();
1291        }
1292
1293        /**
1294         * Create a request-last-frame-numbers holder with a request ID and last regular frame
1295         * number.
1296         */
1297        public RequestLastFrameNumbersHolder(int requestId, long lastRegularFrameNumber) {
1298            mLastRegularFrameNumber = lastRegularFrameNumber;
1299            mLastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
1300            mRequestId = requestId;
1301        }
1302
1303        /**
1304         * Return the last regular frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if
1305         * it contains no regular request.
1306         */
1307        public long getLastRegularFrameNumber() {
1308            return mLastRegularFrameNumber;
1309        }
1310
1311        /**
1312         * Return the last reprocess frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if
1313         * it contains no reprocess request.
1314         */
1315        public long getLastReprocessFrameNumber() {
1316            return mLastReprocessFrameNumber;
1317        }
1318
1319        /**
1320         * Return the last frame number overall.
1321         */
1322        public long getLastFrameNumber() {
1323            return Math.max(mLastRegularFrameNumber, mLastReprocessFrameNumber);
1324        }
1325
1326        /**
1327         * Return the request ID.
1328         */
1329        public int getRequestId() {
1330            return mRequestId;
1331        }
1332    }
1333
1334    /**
1335     * This class tracks the last frame number for submitted requests.
1336     */
1337    public class FrameNumberTracker {
1338
1339        private long mCompletedFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
1340        private long mCompletedReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
1341        /** the skipped frame numbers that belong to regular results */
1342        private final LinkedList<Long> mSkippedRegularFrameNumbers = new LinkedList<Long>();
1343        /** the skipped frame numbers that belong to reprocess results */
1344        private final LinkedList<Long> mSkippedReprocessFrameNumbers = new LinkedList<Long>();
1345        /** frame number -> is reprocess */
1346        private final TreeMap<Long, Boolean> mFutureErrorMap = new TreeMap<Long, Boolean>();
1347        /** Map frame numbers to list of partial results */
1348        private final HashMap<Long, List<CaptureResult>> mPartialResults = new HashMap<>();
1349
1350        private void update() {
1351            Iterator iter = mFutureErrorMap.entrySet().iterator();
1352            while (iter.hasNext()) {
1353                TreeMap.Entry pair = (TreeMap.Entry)iter.next();
1354                Long errorFrameNumber = (Long)pair.getKey();
1355                Boolean reprocess = (Boolean)pair.getValue();
1356                Boolean removeError = true;
1357                if (reprocess) {
1358                    if (errorFrameNumber == mCompletedReprocessFrameNumber + 1) {
1359                        mCompletedReprocessFrameNumber = errorFrameNumber;
1360                    } else if (mSkippedReprocessFrameNumbers.isEmpty() != true &&
1361                            errorFrameNumber == mSkippedReprocessFrameNumbers.element()) {
1362                        mCompletedReprocessFrameNumber = errorFrameNumber;
1363                        mSkippedReprocessFrameNumbers.remove();
1364                    } else {
1365                        removeError = false;
1366                    }
1367                } else {
1368                    if (errorFrameNumber == mCompletedFrameNumber + 1) {
1369                        mCompletedFrameNumber = errorFrameNumber;
1370                    } else if (mSkippedRegularFrameNumbers.isEmpty() != true &&
1371                            errorFrameNumber == mSkippedRegularFrameNumbers.element()) {
1372                        mCompletedFrameNumber = errorFrameNumber;
1373                        mSkippedRegularFrameNumbers.remove();
1374                    } else {
1375                        removeError = false;
1376                    }
1377                }
1378                if (removeError) {
1379                    iter.remove();
1380                }
1381            }
1382        }
1383
1384        /**
1385         * This function is called every time when a result or an error is received.
1386         * @param frameNumber the frame number corresponding to the result or error
1387         * @param isError true if it is an error, false if it is not an error
1388         * @param isReprocess true if it is a reprocess result, false if it is a regular result.
1389         */
1390        public void updateTracker(long frameNumber, boolean isError, boolean isReprocess) {
1391            if (isError) {
1392                mFutureErrorMap.put(frameNumber, isReprocess);
1393            } else {
1394                try {
1395                    if (isReprocess) {
1396                        updateCompletedReprocessFrameNumber(frameNumber);
1397                    } else {
1398                        updateCompletedFrameNumber(frameNumber);
1399                    }
1400                } catch (IllegalArgumentException e) {
1401                    Log.e(TAG, e.getMessage());
1402                }
1403            }
1404            update();
1405        }
1406
1407        /**
1408         * This function is called every time a result has been completed.
1409         *
1410         * <p>It keeps a track of all the partial results already created for a particular
1411         * frame number.</p>
1412         *
1413         * @param frameNumber the frame number corresponding to the result
1414         * @param result the total or partial result
1415         * @param partial {@true} if the result is partial, {@code false} if total
1416         * @param isReprocess true if it is a reprocess result, false if it is a regular result.
1417         */
1418        public void updateTracker(long frameNumber, CaptureResult result, boolean partial,
1419                boolean isReprocess) {
1420            if (!partial) {
1421                // Update the total result's frame status as being successful
1422                updateTracker(frameNumber, /*isError*/false, isReprocess);
1423                // Don't keep a list of total results, we don't need to track them
1424                return;
1425            }
1426
1427            if (result == null) {
1428                // Do not record blank results; this also means there will be no total result
1429                // so it doesn't matter that the partials were not recorded
1430                return;
1431            }
1432
1433            // Partial results must be aggregated in-order for that frame number
1434            List<CaptureResult> partials = mPartialResults.get(frameNumber);
1435            if (partials == null) {
1436                partials = new ArrayList<>();
1437                mPartialResults.put(frameNumber, partials);
1438            }
1439
1440            partials.add(result);
1441        }
1442
1443        /**
1444         * Attempt to pop off all of the partial results seen so far for the {@code frameNumber}.
1445         *
1446         * <p>Once popped-off, the partial results are forgotten (unless {@code updateTracker}
1447         * is called again with new partials for that frame number).</p>
1448         *
1449         * @param frameNumber the frame number corresponding to the result
1450         * @return a list of partial results for that frame with at least 1 element,
1451         *         or {@code null} if there were no partials recorded for that frame
1452         */
1453        public List<CaptureResult> popPartialResults(long frameNumber) {
1454            return mPartialResults.remove(frameNumber);
1455        }
1456
1457        public long getCompletedFrameNumber() {
1458            return mCompletedFrameNumber;
1459        }
1460
1461        public long getCompletedReprocessFrameNumber() {
1462            return mCompletedReprocessFrameNumber;
1463        }
1464
1465        /**
1466         * Update the completed frame number for regular results.
1467         *
1468         * It validates that all previous frames have arrived except for reprocess frames.
1469         *
1470         * If there is a gap since previous regular frame number, assume the frames in the gap are
1471         * reprocess frames and store them in the skipped reprocess frame number queue to check
1472         * against when reprocess frames arrive.
1473         */
1474        private void updateCompletedFrameNumber(long frameNumber) throws IllegalArgumentException {
1475            if (frameNumber <= mCompletedFrameNumber) {
1476                throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat");
1477            } else if (frameNumber <= mCompletedReprocessFrameNumber) {
1478                // if frame number is smaller than completed reprocess frame number,
1479                // it must be the head of mSkippedRegularFrameNumbers
1480                if (mSkippedRegularFrameNumbers.isEmpty() == true ||
1481                        frameNumber < mSkippedRegularFrameNumbers.element()) {
1482                    throw new IllegalArgumentException("frame number " + frameNumber +
1483                            " is a repeat");
1484                } else if (frameNumber > mSkippedRegularFrameNumbers.element()) {
1485                    throw new IllegalArgumentException("frame number " + frameNumber +
1486                            " comes out of order. Expecting " +
1487                            mSkippedRegularFrameNumbers.element());
1488                }
1489                // frame number matches the head of the skipped frame number queue.
1490                mSkippedRegularFrameNumbers.remove();
1491            } else {
1492                // there is a gap of unseen frame numbers which should belong to reprocess result
1493                // put all the skipped frame numbers in the queue
1494                for (long i = Math.max(mCompletedFrameNumber, mCompletedReprocessFrameNumber) + 1;
1495                        i < frameNumber; i++) {
1496                    mSkippedReprocessFrameNumbers.add(i);
1497                }
1498            }
1499
1500            mCompletedFrameNumber = frameNumber;
1501        }
1502
1503        /**
1504         * Update the completed frame number for reprocess results.
1505         *
1506         * It validates that all previous frames have arrived except for regular frames.
1507         *
1508         * If there is a gap since previous reprocess frame number, assume the frames in the gap are
1509         * regular frames and store them in the skipped regular frame number queue to check
1510         * against when regular frames arrive.
1511         */
1512        private void updateCompletedReprocessFrameNumber(long frameNumber)
1513                throws IllegalArgumentException {
1514            if (frameNumber < mCompletedReprocessFrameNumber) {
1515                throw new IllegalArgumentException("frame number " + frameNumber + " is a repeat");
1516            } else if (frameNumber < mCompletedFrameNumber) {
1517                // if reprocess frame number is smaller than completed regular frame number,
1518                // it must be the head of the skipped reprocess frame number queue.
1519                if (mSkippedReprocessFrameNumbers.isEmpty() == true ||
1520                        frameNumber < mSkippedReprocessFrameNumbers.element()) {
1521                    throw new IllegalArgumentException("frame number " + frameNumber +
1522                            " is a repeat");
1523                } else if (frameNumber > mSkippedReprocessFrameNumbers.element()) {
1524                    throw new IllegalArgumentException("frame number " + frameNumber +
1525                            " comes out of order. Expecting " +
1526                            mSkippedReprocessFrameNumbers.element());
1527                }
1528                // frame number matches the head of the skipped frame number queue.
1529                mSkippedReprocessFrameNumbers.remove();
1530            } else {
1531                // put all the skipped frame numbers in the queue
1532                for (long i = Math.max(mCompletedFrameNumber, mCompletedReprocessFrameNumber) + 1;
1533                        i < frameNumber; i++) {
1534                    mSkippedRegularFrameNumbers.add(i);
1535                }
1536            }
1537            mCompletedReprocessFrameNumber = frameNumber;
1538        }
1539    }
1540
1541    private void checkAndFireSequenceComplete() {
1542        long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber();
1543        long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber();
1544        boolean isReprocess = false;
1545        Iterator<RequestLastFrameNumbersHolder> iter = mRequestLastFrameNumbersList.iterator();
1546        while (iter.hasNext()) {
1547            final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next();
1548            boolean sequenceCompleted = false;
1549            final int requestId = requestLastFrameNumbers.getRequestId();
1550            final CaptureCallbackHolder holder;
1551            synchronized(mInterfaceLock) {
1552                if (mRemoteDevice == null) {
1553                    Log.w(TAG, "Camera closed while checking sequences");
1554                    return;
1555                }
1556
1557                int index = mCaptureCallbackMap.indexOfKey(requestId);
1558                holder = (index >= 0) ?
1559                        mCaptureCallbackMap.valueAt(index) : null;
1560                if (holder != null) {
1561                    long lastRegularFrameNumber =
1562                            requestLastFrameNumbers.getLastRegularFrameNumber();
1563                    long lastReprocessFrameNumber =
1564                            requestLastFrameNumbers.getLastReprocessFrameNumber();
1565
1566                    // check if it's okay to remove request from mCaptureCallbackMap
1567                    if (lastRegularFrameNumber <= completedFrameNumber &&
1568                            lastReprocessFrameNumber <= completedReprocessFrameNumber) {
1569                        sequenceCompleted = true;
1570                        mCaptureCallbackMap.removeAt(index);
1571                        if (DEBUG) {
1572                            Log.v(TAG, String.format(
1573                                    "Remove holder for requestId %d, because lastRegularFrame %d " +
1574                                    "is <= %d and lastReprocessFrame %d is <= %d", requestId,
1575                                    lastRegularFrameNumber, completedFrameNumber,
1576                                    lastReprocessFrameNumber, completedReprocessFrameNumber));
1577                        }
1578                    }
1579                }
1580            }
1581
1582            // If no callback is registered for this requestId or sequence completed, remove it
1583            // from the frame number->request pair because it's not needed anymore.
1584            if (holder == null || sequenceCompleted) {
1585                iter.remove();
1586            }
1587
1588            // Call onCaptureSequenceCompleted
1589            if (sequenceCompleted) {
1590                Runnable resultDispatch = new Runnable() {
1591                    @Override
1592                    public void run() {
1593                        if (!CameraDeviceImpl.this.isClosed()){
1594                            if (DEBUG) {
1595                                Log.d(TAG, String.format(
1596                                        "fire sequence complete for request %d",
1597                                        requestId));
1598                            }
1599
1600                            holder.getCallback().onCaptureSequenceCompleted(
1601                                CameraDeviceImpl.this,
1602                                requestId,
1603                                requestLastFrameNumbers.getLastFrameNumber());
1604                        }
1605                    }
1606                };
1607                holder.getHandler().post(resultDispatch);
1608            }
1609        }
1610    }
1611
1612    public class CameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
1613
1614        @Override
1615        public IBinder asBinder() {
1616            return this;
1617        }
1618
1619        @Override
1620        public void onDeviceError(final int errorCode, CaptureResultExtras resultExtras) {
1621            if (DEBUG) {
1622                Log.d(TAG, String.format(
1623                        "Device error received, code %d, frame number %d, request ID %d, subseq ID %d",
1624                        errorCode, resultExtras.getFrameNumber(), resultExtras.getRequestId(),
1625                        resultExtras.getSubsequenceId()));
1626            }
1627
1628            synchronized(mInterfaceLock) {
1629                if (mRemoteDevice == null) {
1630                    return; // Camera already closed
1631                }
1632
1633                switch (errorCode) {
1634                    case ERROR_CAMERA_DISCONNECTED:
1635                        CameraDeviceImpl.this.mDeviceHandler.post(mCallOnDisconnected);
1636                        break;
1637                    default:
1638                        Log.e(TAG, "Unknown error from camera device: " + errorCode);
1639                        // no break
1640                    case ERROR_CAMERA_DEVICE:
1641                    case ERROR_CAMERA_SERVICE:
1642                        mInError = true;
1643                        final int publicErrorCode = (errorCode == ERROR_CAMERA_DEVICE) ?
1644                                StateCallback.ERROR_CAMERA_DEVICE :
1645                                StateCallback.ERROR_CAMERA_SERVICE;
1646                        Runnable r = new Runnable() {
1647                            @Override
1648                            public void run() {
1649                                if (!CameraDeviceImpl.this.isClosed()) {
1650                                    mDeviceCallback.onError(CameraDeviceImpl.this, publicErrorCode);
1651                                }
1652                            }
1653                        };
1654                        CameraDeviceImpl.this.mDeviceHandler.post(r);
1655                        break;
1656                    case ERROR_CAMERA_REQUEST:
1657                    case ERROR_CAMERA_RESULT:
1658                    case ERROR_CAMERA_BUFFER:
1659                        onCaptureErrorLocked(errorCode, resultExtras);
1660                        break;
1661                }
1662            }
1663        }
1664
1665        @Override
1666        public void onRepeatingRequestError(long lastFrameNumber) {
1667            if (DEBUG) {
1668                Log.d(TAG, "Repeating request error received. Last frame number is " +
1669                        lastFrameNumber);
1670            }
1671
1672            synchronized(mInterfaceLock) {
1673                // Camera is already closed or no repeating request is present.
1674                if (mRemoteDevice == null || mRepeatingRequestId == REQUEST_ID_NONE) {
1675                    return; // Camera already closed
1676                }
1677
1678                checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
1679                mRepeatingRequestId = REQUEST_ID_NONE;
1680            }
1681        }
1682
1683        @Override
1684        public void onDeviceIdle() {
1685            if (DEBUG) {
1686                Log.d(TAG, "Camera now idle");
1687            }
1688            synchronized(mInterfaceLock) {
1689                if (mRemoteDevice == null) return; // Camera already closed
1690
1691                if (!CameraDeviceImpl.this.mIdle) {
1692                    CameraDeviceImpl.this.mDeviceHandler.post(mCallOnIdle);
1693                }
1694                CameraDeviceImpl.this.mIdle = true;
1695            }
1696        }
1697
1698        @Override
1699        public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) {
1700            int requestId = resultExtras.getRequestId();
1701            final long frameNumber = resultExtras.getFrameNumber();
1702
1703            if (DEBUG) {
1704                Log.d(TAG, "Capture started for id " + requestId + " frame number " + frameNumber);
1705            }
1706            final CaptureCallbackHolder holder;
1707
1708            synchronized(mInterfaceLock) {
1709                if (mRemoteDevice == null) return; // Camera already closed
1710
1711                // Get the callback for this frame ID, if there is one
1712                holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
1713
1714                if (holder == null) {
1715                    return;
1716                }
1717
1718                if (isClosed()) return;
1719
1720                // Dispatch capture start notice
1721                holder.getHandler().post(
1722                    new Runnable() {
1723                        @Override
1724                        public void run() {
1725                            if (!CameraDeviceImpl.this.isClosed()) {
1726                                holder.getCallback().onCaptureStarted(
1727                                    CameraDeviceImpl.this,
1728                                    holder.getRequest(resultExtras.getSubsequenceId()),
1729                                    timestamp, frameNumber);
1730                            }
1731                        }
1732                    });
1733
1734            }
1735        }
1736
1737        @Override
1738        public void onResultReceived(CameraMetadataNative result,
1739                CaptureResultExtras resultExtras) throws RemoteException {
1740
1741            int requestId = resultExtras.getRequestId();
1742            long frameNumber = resultExtras.getFrameNumber();
1743
1744            if (DEBUG) {
1745                Log.v(TAG, "Received result frame " + frameNumber + " for id "
1746                        + requestId);
1747            }
1748
1749            synchronized(mInterfaceLock) {
1750                if (mRemoteDevice == null) return; // Camera already closed
1751
1752                // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
1753                result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
1754                        getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
1755
1756                final CaptureCallbackHolder holder =
1757                        CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
1758                final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
1759
1760                boolean isPartialResult =
1761                        (resultExtras.getPartialResultCount() < mTotalPartialCount);
1762                boolean isReprocess = request.isReprocess();
1763
1764                // Check if we have a callback for this
1765                if (holder == null) {
1766                    if (DEBUG) {
1767                        Log.d(TAG,
1768                                "holder is null, early return at frame "
1769                                        + frameNumber);
1770                    }
1771
1772                    mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
1773                            isReprocess);
1774
1775                    return;
1776                }
1777
1778                if (isClosed()) {
1779                    if (DEBUG) {
1780                        Log.d(TAG,
1781                                "camera is closed, early return at frame "
1782                                        + frameNumber);
1783                    }
1784
1785                    mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
1786                            isReprocess);
1787                    return;
1788                }
1789
1790
1791                Runnable resultDispatch = null;
1792
1793                CaptureResult finalResult;
1794
1795                // Either send a partial result or the final capture completed result
1796                if (isPartialResult) {
1797                    final CaptureResult resultAsCapture =
1798                            new CaptureResult(result, request, resultExtras);
1799
1800                    // Partial result
1801                    resultDispatch = new Runnable() {
1802                        @Override
1803                        public void run() {
1804                            if (!CameraDeviceImpl.this.isClosed()){
1805                                holder.getCallback().onCaptureProgressed(
1806                                    CameraDeviceImpl.this,
1807                                    request,
1808                                    resultAsCapture);
1809                            }
1810                        }
1811                    };
1812
1813                    finalResult = resultAsCapture;
1814                } else {
1815                    List<CaptureResult> partialResults =
1816                            mFrameNumberTracker.popPartialResults(frameNumber);
1817
1818                    final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result,
1819                            request, resultExtras, partialResults, holder.getSessionId());
1820
1821                    // Final capture result
1822                    resultDispatch = new Runnable() {
1823                        @Override
1824                        public void run() {
1825                            if (!CameraDeviceImpl.this.isClosed()){
1826                                holder.getCallback().onCaptureCompleted(
1827                                    CameraDeviceImpl.this,
1828                                    request,
1829                                    resultAsCapture);
1830                            }
1831                        }
1832                    };
1833
1834                    finalResult = resultAsCapture;
1835                }
1836
1837                holder.getHandler().post(resultDispatch);
1838
1839                // Collect the partials for a total result; or mark the frame as totally completed
1840                mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult,
1841                        isReprocess);
1842
1843                // Fire onCaptureSequenceCompleted
1844                if (!isPartialResult) {
1845                    checkAndFireSequenceComplete();
1846                }
1847            }
1848        }
1849
1850        @Override
1851        public void onPrepared(int streamId) {
1852            final OutputConfiguration output;
1853            final StateCallbackKK sessionCallback;
1854
1855            if (DEBUG) {
1856                Log.v(TAG, "Stream " + streamId + " is prepared");
1857            }
1858
1859            synchronized(mInterfaceLock) {
1860                output = mConfiguredOutputs.get(streamId);
1861                sessionCallback = mSessionStateCallback;
1862            }
1863
1864            if (sessionCallback == null) return;
1865
1866            if (output == null) {
1867                Log.w(TAG, "onPrepared invoked for unknown output Surface");
1868                return;
1869            }
1870            final Surface surface = output.getSurface();
1871
1872            sessionCallback.onSurfacePrepared(surface);
1873        }
1874
1875        /**
1876         * Called by onDeviceError for handling single-capture failures.
1877         */
1878        private void onCaptureErrorLocked(int errorCode, CaptureResultExtras resultExtras) {
1879
1880            final int requestId = resultExtras.getRequestId();
1881            final int subsequenceId = resultExtras.getSubsequenceId();
1882            final long frameNumber = resultExtras.getFrameNumber();
1883            final CaptureCallbackHolder holder =
1884                    CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
1885
1886            final CaptureRequest request = holder.getRequest(subsequenceId);
1887
1888            Runnable failureDispatch = null;
1889            if (errorCode == ERROR_CAMERA_BUFFER) {
1890                final Surface outputSurface =
1891                        mConfiguredOutputs.get(resultExtras.getErrorStreamId()).getSurface();
1892                if (DEBUG) {
1893                    Log.v(TAG, String.format("Lost output buffer reported for frame %d, target %s",
1894                            frameNumber, outputSurface));
1895                }
1896                failureDispatch = new Runnable() {
1897                    @Override
1898                    public void run() {
1899                        if (!CameraDeviceImpl.this.isClosed()){
1900                            holder.getCallback().onCaptureBufferLost(
1901                                CameraDeviceImpl.this,
1902                                request,
1903                                outputSurface,
1904                                frameNumber);
1905                        }
1906                    }
1907                };
1908            } else {
1909                boolean mayHaveBuffers = (errorCode == ERROR_CAMERA_RESULT);
1910
1911                // This is only approximate - exact handling needs the camera service and HAL to
1912                // disambiguate between request failures to due abort and due to real errors.  For
1913                // now, assume that if the session believes we're mid-abort, then the error is due
1914                // to abort.
1915                int reason = (mCurrentSession != null && mCurrentSession.isAborting()) ?
1916                        CaptureFailure.REASON_FLUSHED :
1917                        CaptureFailure.REASON_ERROR;
1918
1919                final CaptureFailure failure = new CaptureFailure(
1920                    request,
1921                    reason,
1922                    /*dropped*/ mayHaveBuffers,
1923                    requestId,
1924                    frameNumber);
1925
1926                failureDispatch = new Runnable() {
1927                    @Override
1928                    public void run() {
1929                        if (!CameraDeviceImpl.this.isClosed()){
1930                            holder.getCallback().onCaptureFailed(
1931                                CameraDeviceImpl.this,
1932                                request,
1933                                failure);
1934                        }
1935                    }
1936                };
1937
1938                // Fire onCaptureSequenceCompleted if appropriate
1939                if (DEBUG) {
1940                    Log.v(TAG, String.format("got error frame %d", frameNumber));
1941                }
1942                mFrameNumberTracker.updateTracker(frameNumber, /*error*/true, request.isReprocess());
1943                checkAndFireSequenceComplete();
1944            }
1945
1946            // Dispatch the failure callback
1947            holder.getHandler().post(failureDispatch);
1948        }
1949
1950    } // public class CameraDeviceCallbacks
1951
1952    /**
1953     * Default handler management.
1954     *
1955     * <p>
1956     * If handler is null, get the current thread's
1957     * Looper to create a Handler with. If no looper exists, throw {@code IllegalArgumentException}.
1958     * </p>
1959     */
1960    static Handler checkHandler(Handler handler) {
1961        if (handler == null) {
1962            Looper looper = Looper.myLooper();
1963            if (looper == null) {
1964                throw new IllegalArgumentException(
1965                    "No handler given, and current thread has no looper!");
1966            }
1967            handler = new Handler(looper);
1968        }
1969        return handler;
1970    }
1971
1972    /**
1973     * Default handler management, conditional on there being a callback.
1974     *
1975     * <p>If the callback isn't null, check the handler, otherwise pass it through.</p>
1976     */
1977    static <T> Handler checkHandler(Handler handler, T callback) {
1978        if (callback != null) {
1979            return checkHandler(handler);
1980        }
1981        return handler;
1982    }
1983
1984    private void checkIfCameraClosedOrInError() throws CameraAccessException {
1985        if (mRemoteDevice == null) {
1986            throw new IllegalStateException("CameraDevice was already closed");
1987        }
1988        if (mInError) {
1989            throw new CameraAccessException(CameraAccessException.CAMERA_ERROR,
1990                    "The camera device has encountered a serious error");
1991        }
1992    }
1993
1994    /** Whether the camera device has started to close (may not yet have finished) */
1995    private boolean isClosed() {
1996        return mClosing.get();
1997    }
1998
1999    private CameraCharacteristics getCharacteristics() {
2000        return mCharacteristics;
2001    }
2002
2003    /**
2004     * Listener for binder death.
2005     *
2006     * <p> Handle binder death for ICameraDeviceUser. Trigger onError.</p>
2007     */
2008    public void binderDied() {
2009        Log.w(TAG, "CameraDevice " + mCameraId + " died unexpectedly");
2010
2011        if (mRemoteDevice == null) {
2012            return; // Camera already closed
2013        }
2014
2015        mInError = true;
2016        Runnable r = new Runnable() {
2017            @Override
2018            public void run() {
2019                if (!isClosed()) {
2020                    mDeviceCallback.onError(CameraDeviceImpl.this,
2021                            StateCallback.ERROR_CAMERA_SERVICE);
2022                }
2023            }
2024        };
2025        CameraDeviceImpl.this.mDeviceHandler.post(r);
2026    }
2027}
2028