OSDN Git Service

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