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