1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.testingcamera2.v1;
18 
19 import android.content.Context;
20 import android.graphics.ImageFormat;
21 import android.hardware.camera2.CameraAccessException;
22 import android.hardware.camera2.CameraCaptureSession;
23 import android.hardware.camera2.CameraDevice;
24 import android.hardware.camera2.CameraManager;
25 import android.hardware.camera2.CameraMetadata;
26 import android.hardware.camera2.CameraCharacteristics;
27 import android.hardware.camera2.CaptureRequest;
28 import android.hardware.camera2.CaptureRequest.Builder;
29 import android.hardware.camera2.params.OutputConfiguration;
30 import android.util.Size;
31 import android.media.Image;
32 import android.media.ImageReader;
33 import android.media.MediaCodec;
34 import android.os.ConditionVariable;
35 import android.os.Handler;
36 import android.os.HandlerThread;
37 import android.util.Log;
38 import android.util.Size;
39 import android.view.Surface;
40 import android.view.SurfaceHolder;
41 
42 import com.android.ex.camera2.blocking.BlockingCameraManager;
43 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
44 import com.android.ex.camera2.blocking.BlockingStateCallback;
45 import com.android.ex.camera2.blocking.BlockingSessionCallback;
46 
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.List;
50 import java.util.Set;
51 
52 /**
53  * A camera controller class that runs in its own thread, to
54  * move camera ops off the UI. Generally thread-safe.
55  */
56 public class CameraOps {
57 
58     public static interface Listener {
onCameraOpened(String cameraId, CameraCharacteristics characteristics)59         void onCameraOpened(String cameraId, CameraCharacteristics characteristics);
60     }
61 
62     private static final String TAG = "CameraOps";
63     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
64 
65     private final HandlerThread mOpsThread;
66     private final Handler mOpsHandler;
67 
68     private final CameraManager mCameraManager;
69     private final BlockingCameraManager mBlockingCameraManager;
70     private final BlockingStateCallback mDeviceListener =
71             new BlockingStateCallback();
72 
73     private String mCameraId;
74     private CameraDevice mCamera;
75     private CameraCaptureSession mSession;
76 
77     private ImageReader mCaptureReader;
78     private CameraCharacteristics mCameraCharacteristics;
79 
80     private int mEncodingBitRate;
81     private int mDeviceOrientation;
82 
83     private CaptureRequest.Builder mPreviewRequestBuilder;
84     private CaptureRequest.Builder mRecordingRequestBuilder;
85     List<Surface> mOutputSurfaces = new ArrayList<Surface>(2);
86     private Surface mPreviewSurface;
87     private Surface mPreviewSurface2;
88     // How many still capture buffers do we want to hold on to at once
89     private static final int MAX_CONCURRENT_STILL_CAPTURES = 2;
90 
91     private static final int STATUS_ERROR = 0;
92     private static final int STATUS_UNINITIALIZED = 1;
93     private static final int STATUS_OK = 2;
94     // low encoding bitrate(bps), used by small resolution like 640x480.
95     private static final int ENC_BIT_RATE_LOW = 2000000;
96     // high encoding bitrate(bps), used by large resolution like 1080p.
97     private static final int ENC_BIT_RATE_HIGH = 10000000;
98     private static final Size DEFAULT_SIZE = new Size(640, 480);
99     private static final Size HIGH_RESOLUTION_SIZE = new Size(1920, 1080);
100 
101     private static final long IDLE_WAIT_MS = 2000;
102     // General short wait timeout for most state transitions
103     private static final long STATE_WAIT_MS = 500;
104 
105     private int mStatus = STATUS_UNINITIALIZED;
106 
107     CameraRecordingStream mRecordingStream;
108     private final Listener mListener;
109     private final Handler mListenerHandler;
110 
111     // Physical camera id of the current logical multi-camera. "" if this is not a logical
112     // multi-camera.
113     private String mPhysicalCameraId1;
114     private String mPhysicalCameraId2;
115 
checkOk()116     private void checkOk() {
117         if (mStatus < STATUS_OK) {
118             throw new IllegalStateException(String.format("Device not OK: %d", mStatus ));
119         }
120     }
121 
CameraOps(Context ctx, Listener listener, Handler handler)122     private CameraOps(Context ctx, Listener listener, Handler handler) throws ApiFailureException {
123         mCameraManager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE);
124         if (mCameraManager == null) {
125             throw new ApiFailureException("Can't connect to camera manager!");
126         }
127         mBlockingCameraManager = new BlockingCameraManager(mCameraManager);
128 
129         mOpsThread = new HandlerThread("CameraOpsThread");
130         mOpsThread.start();
131         mOpsHandler = new Handler(mOpsThread.getLooper());
132 
133         mRecordingStream = new CameraRecordingStream();
134         mStatus = STATUS_OK;
135 
136         mListener = listener;
137         mListenerHandler = handler;
138     }
139 
create(Context ctx, Listener listener, Handler handler)140     static public CameraOps create(Context ctx, Listener listener, Handler handler)
141             throws ApiFailureException {
142         return new CameraOps(ctx, listener, handler);
143     }
144 
getDevices()145     public String[] getDevices() throws ApiFailureException{
146         checkOk();
147         try {
148             return mCameraManager.getCameraIdList();
149         } catch (CameraAccessException e) {
150             throw new ApiFailureException("Can't query device set", e);
151         }
152     }
153 
registerCameraListener(CameraManager.AvailabilityCallback listener)154     public void registerCameraListener(CameraManager.AvailabilityCallback listener)
155             throws ApiFailureException {
156         checkOk();
157         mCameraManager.registerAvailabilityCallback(listener, mOpsHandler);
158     }
159 
getCameraCharacteristics()160     public CameraCharacteristics getCameraCharacteristics() {
161         checkOk();
162         if (mCameraCharacteristics == null) {
163             throw new IllegalStateException("CameraCharacteristics is not available");
164         }
165         return mCameraCharacteristics;
166     }
167 
closeDevice()168     public void closeDevice()
169             throws ApiFailureException {
170         checkOk();
171         mCameraCharacteristics = null;
172 
173         if (mCamera == null) return;
174 
175         try {
176             mCamera.close();
177         } catch (Exception e) {
178             throw new ApiFailureException("can't close device!", e);
179         }
180 
181         mCamera = null;
182         mSession = null;
183     }
184 
minimalOpenCamera()185     private void minimalOpenCamera() throws ApiFailureException {
186         // Open camera if not yet opened, or the currently opened camera is not the right one.
187         if (mCamera == null || !mCameraId.equals(mCamera.getId())) {
188             closeDevice();
189 
190             mPhysicalCameraId1 = "";
191             mPhysicalCameraId2 = "";
192             final CameraCharacteristics characteristics;
193             try {
194                 CameraCharacteristics c = mCameraManager.getCameraCharacteristics(mCameraId);
195                 int[] caps = c.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
196                 for (int cap : caps) {
197                     if (CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
198                             != cap) {
199                         continue;
200                     }
201 
202                     Set<String> physicalIds = c.getPhysicalCameraIds();
203                     if (physicalIds.size() != 2) {
204                         throw new ApiFailureException(
205                                 "3 or more physical cameras are not yet supported");
206                     }
207                     String[] physicalIdsArray = physicalIds.toArray(new String[2]);
208                     mPhysicalCameraId1 = physicalIdsArray[0];
209                     mPhysicalCameraId2 = physicalIdsArray[1];
210                     break;
211                 }
212                 Log.i(TAG, "Opening " + mCameraId);
213                 mCamera = mBlockingCameraManager.openCamera(mCameraId,
214                         mDeviceListener, mOpsHandler);
215                 mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId);
216                 characteristics = mCameraCharacteristics;
217             } catch (CameraAccessException e) {
218                 throw new ApiFailureException("open failure", e);
219             } catch (BlockingOpenException e) {
220                 throw new ApiFailureException("open async failure", e);
221             }
222 
223             // Dispatch listener event
224             if (mListener != null && mListenerHandler != null) {
225                 mListenerHandler.post(new Runnable() {
226                     @Override
227                     public void run() {
228                         mListener.onCameraOpened(mCameraId, characteristics);
229                     }
230                 });
231             }
232         }
233 
234         mStatus = STATUS_OK;
235     }
236 
configureOutputs(List<Surface> outputs)237     private void configureOutputs(List<Surface> outputs) throws CameraAccessException {
238         BlockingSessionCallback sessionListener = new BlockingSessionCallback();
239         mCamera.createCaptureSession(outputs, sessionListener, mOpsHandler);
240         mSession = sessionListener.waitAndGetSession(IDLE_WAIT_MS);
241     }
242 
configureOutputsByConfigs(List<OutputConfiguration> outputConfigs)243     private void configureOutputsByConfigs(List<OutputConfiguration> outputConfigs)
244             throws CameraAccessException {
245         BlockingSessionCallback sessionListener = new BlockingSessionCallback();
246         mCamera.createCaptureSessionByOutputConfigurations(outputConfigs, sessionListener, mOpsHandler);
247         mSession = sessionListener.waitAndGetSession(IDLE_WAIT_MS);
248     }
249 
250     /**
251      * Set up SurfaceView dimensions for camera preview
252      */
minimalPreviewConfig(String cameraId, SurfaceHolder previewHolder, SurfaceHolder previewHolder2)253     public void minimalPreviewConfig(String cameraId, SurfaceHolder previewHolder,
254             SurfaceHolder previewHolder2) throws ApiFailureException {
255 
256         mCameraId = cameraId;
257         minimalOpenCamera();
258         try {
259             CameraCharacteristics properties =
260                     mCameraManager.getCameraCharacteristics(mCamera.getId());
261 
262             Size[] previewSizes = null;
263             Size sz = DEFAULT_SIZE;
264             if (properties != null) {
265                 previewSizes =
266                         properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).
267                         getOutputSizes(previewHolder.getClass());
268             }
269 
270             if (previewSizes != null && previewSizes.length != 0 &&
271                     Arrays.asList(previewSizes).contains(HIGH_RESOLUTION_SIZE)) {
272                 sz = HIGH_RESOLUTION_SIZE;
273             }
274             Log.i(TAG, "Set preview size to " + sz.toString());
275             previewHolder.setFixedSize(sz.getWidth(), sz.getHeight());
276             previewHolder2.setFixedSize(sz.getWidth(), sz.getHeight());
277             mPreviewSurface = previewHolder.getSurface();
278             mPreviewSurface2 = previewHolder2.getSurface();
279         }  catch (CameraAccessException e) {
280             throw new ApiFailureException("Error setting up minimal preview", e);
281         }
282     }
283 
284 
285     /**
286      * Update current preview with user-specified control inputs.
287      */
updatePreview(CameraControls controls)288     public void updatePreview(CameraControls controls) {
289         if (VERBOSE) {
290             Log.v(TAG, "updatePreview - begin");
291         }
292 
293         updateCaptureRequest(mPreviewRequestBuilder, controls);
294 
295         try {
296             // Insert a one-time request if any triggers were set into the request
297             if (hasTriggers(mPreviewRequestBuilder)) {
298                 mSession.capture(mPreviewRequestBuilder.build(), /*listener*/null, /*handler*/null);
299                 removeTriggers(mPreviewRequestBuilder);
300 
301                 if (VERBOSE) {
302                     Log.v(TAG, "updatePreview - submitted extra one-shot capture with triggers");
303                 }
304             } else {
305                 if (VERBOSE) {
306                     Log.v(TAG, "updatePreview - no triggers, regular repeating request");
307                 }
308             }
309 
310             // TODO: add capture result listener
311             mSession.setRepeatingRequest(mPreviewRequestBuilder.build(),
312                     /*listener*/null, /*handler*/null);
313         } catch (CameraAccessException e) {
314             Log.e(TAG, "Update camera preview failed");
315         }
316 
317         if (VERBOSE) {
318             Log.v(TAG, "updatePreview - end");
319         }
320     }
321 
hasTriggers(Builder requestBuilder)322     private static boolean hasTriggers(Builder requestBuilder) {
323         if (requestBuilder == null) {
324             return false;
325         }
326 
327         Integer afTrigger = requestBuilder.get(CaptureRequest.CONTROL_AF_TRIGGER);
328         Integer aePrecaptureTrigger = requestBuilder.get(
329                 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER);
330 
331         if (VERBOSE) {
332             Log.v(TAG, String.format("hasTriggers - afTrigger = %s, aePreCaptureTrigger = %s",
333                     afTrigger, aePrecaptureTrigger));
334         }
335 
336 
337         if (afTrigger != null && afTrigger != CaptureRequest.CONTROL_AF_TRIGGER_IDLE) {
338             return true;
339         }
340 
341 
342         if (aePrecaptureTrigger != null
343                 && aePrecaptureTrigger != CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE) {
344             return true;
345         }
346 
347         return false;
348     }
349 
removeTriggers(Builder requestBuilder)350     private static void removeTriggers(Builder requestBuilder) {
351         if (requestBuilder == null) {
352             return;
353         }
354 
355         requestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
356                 CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
357         requestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
358                 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
359     }
360 
361     /**
362      * Update current device orientation (0~360 degrees)
363      */
updateOrientation(int orientation)364     public void updateOrientation(int orientation) {
365         mDeviceOrientation = orientation;
366     }
367 
368     /**
369      * Configure streams and run minimal preview
370      */
minimalPreview(SurfaceHolder previewHolder, SurfaceHolder previewHolder2, CameraControls camCtl)371     public void minimalPreview(SurfaceHolder previewHolder, SurfaceHolder previewHolder2,
372             CameraControls camCtl) throws ApiFailureException {
373 
374         minimalOpenCamera();
375 
376         if (mPreviewSurface == null || mPreviewSurface2 == null) {
377             throw new ApiFailureException("Preview surface is not created");
378         }
379         try {
380             List<OutputConfiguration> outputConfigs =
381                     new ArrayList<OutputConfiguration>(/*capacity*/2);
382             boolean isLogicalCamera =
383                     !mPhysicalCameraId1.equals("") && !mPhysicalCameraId2.equals("");
384             if (isLogicalCamera) {
385                 OutputConfiguration config1 = new OutputConfiguration(previewHolder.getSurface());
386                 config1.setPhysicalCameraId(mPhysicalCameraId1);
387                 outputConfigs.add(config1);
388 
389                 OutputConfiguration config2 = new OutputConfiguration(previewHolder2.getSurface());
390                 config2.setPhysicalCameraId(mPhysicalCameraId2);
391                 outputConfigs.add(config2);
392             } else {
393                 OutputConfiguration config = new OutputConfiguration(previewHolder.getSurface());
394                 outputConfigs.add(config);
395             }
396             configureOutputsByConfigs(outputConfigs);
397 
398             mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
399             updateCaptureRequest(mPreviewRequestBuilder, camCtl);
400 
401             mPreviewRequestBuilder.addTarget(mPreviewSurface);
402             if (isLogicalCamera) {
403                 mPreviewRequestBuilder.addTarget(mPreviewSurface2);
404             }
405 
406             mSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null);
407         } catch (CameraAccessException e) {
408             throw new ApiFailureException("Error setting up minimal preview", e);
409         }
410     }
411 
412     private static class SimpleImageListener implements ImageReader.OnImageAvailableListener {
413         private final ConditionVariable imageAvailable = new ConditionVariable();
414         private final CaptureCallback mListener;
415 
SimpleImageListener(final CaptureCallback listener)416         SimpleImageListener(final CaptureCallback listener) {
417             mListener = listener;
418         }
419 
420         @Override
onImageAvailable(ImageReader reader)421         public void onImageAvailable(ImageReader reader) {
422             Image i = null;
423             try {
424                 i = reader.acquireNextImage();
425                 mListener.onCaptureAvailable(i);
426             } finally {
427                 if (i != null) {
428                     i.close();
429                 }
430                 imageAvailable.open();
431             }
432         }
433 
waitForImageAvailable(long timeout)434         public void waitForImageAvailable(long timeout) {
435             if (imageAvailable.block(timeout)) {
436                 imageAvailable.close();
437             } else {
438                 Log.e(TAG, "wait for image available timed out after " + timeout + "ms");
439             }
440         }
441     }
442 
minimalStillCapture(final CaptureCallback listener, CaptureResultListener l, Handler h, CameraControls cameraControl, int format)443     public void minimalStillCapture(final CaptureCallback listener, CaptureResultListener l,
444             Handler h, CameraControls cameraControl, int format) throws ApiFailureException {
445         minimalOpenCamera();
446 
447         try {
448             CameraCharacteristics properties =
449                     mCameraManager.getCameraCharacteristics(mCamera.getId());
450             Size[] stillSizes = null;
451             if (properties != null) {
452                 stillSizes = properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).
453                         getOutputSizes(format);
454             }
455             int width = 640;
456             int height = 480;
457 
458             if (stillSizes != null && stillSizes.length > 0) {
459                 width = stillSizes[0].getWidth();
460                 height = stillSizes[0].getHeight();
461             }
462 
463             if (mCaptureReader == null || mCaptureReader.getWidth() != width ||
464                     mCaptureReader.getHeight() != height ||
465                     mCaptureReader.getImageFormat() != format) {
466                 if (mCaptureReader != null) {
467                     mCaptureReader.close();
468                 }
469                 mCaptureReader = ImageReader.newInstance(width, height,
470                         format, MAX_CONCURRENT_STILL_CAPTURES);
471             }
472 
473             List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
474             outputSurfaces.add(mCaptureReader.getSurface());
475 
476             configureOutputs(outputSurfaces);
477 
478             CaptureRequest.Builder captureBuilder =
479                     mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
480             captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientationHint());
481 
482             captureBuilder.addTarget(mCaptureReader.getSurface());
483 
484             updateCaptureRequest(captureBuilder, cameraControl);
485 
486             SimpleImageListener readerListener = new SimpleImageListener(listener);
487 
488             mCaptureReader.setOnImageAvailableListener(readerListener, h);
489 
490             mSession.capture(captureBuilder.build(), l, mOpsHandler);
491 
492             readerListener.waitForImageAvailable(1000L/*timeout*/);
493         } catch (CameraAccessException e) {
494             throw new ApiFailureException("Error in minimal still capture", e);
495         }
496     }
497 
startRecording(Context applicationContext, boolean useMediaCodec, int outputFormat)498     public void startRecording(Context applicationContext, boolean useMediaCodec, int outputFormat)
499             throws ApiFailureException {
500         minimalOpenCamera();
501         Size recordingSize = getRecordingSize();
502         int orientationHint = getOrientationHint();
503         try {
504             if (mRecordingRequestBuilder == null) {
505                 mRecordingRequestBuilder =
506                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
507             }
508             // Setup output stream first
509             mRecordingStream.configure(
510                     applicationContext, recordingSize, useMediaCodec, mEncodingBitRate,
511                     orientationHint, outputFormat);
512             mRecordingStream.onConfiguringOutputs(mOutputSurfaces, /* detach */false);
513             mRecordingStream.onConfiguringRequest(mRecordingRequestBuilder, /* detach */false);
514 
515             // TODO: For preview, create preview stream class, and do the same thing like recording.
516             mOutputSurfaces.add(mPreviewSurface);
517             mRecordingRequestBuilder.addTarget(mPreviewSurface);
518 
519             // Start camera streaming and recording.
520             configureOutputs(mOutputSurfaces);
521             mSession.setRepeatingRequest(mRecordingRequestBuilder.build(), null, null);
522             mRecordingStream.start();
523         } catch (CameraAccessException e) {
524             throw new ApiFailureException("Error start recording", e);
525         }
526     }
527 
stopRecording(Context ctx)528     public void stopRecording(Context ctx) throws ApiFailureException {
529         try {
530             /**
531              * <p>
532              * Only stop camera recording stream.
533              * </p>
534              * <p>
535              * FIXME: There is a race condition to be fixed in CameraDevice.
536              * Basically, when stream closes, encoder and its surface is
537              * released, while it still takes some time for camera to finish the
538              * output to that surface. Then it cause camera in bad state.
539              * </p>
540              */
541             mRecordingStream.onConfiguringRequest(mRecordingRequestBuilder, /* detach */true);
542             mRecordingStream.onConfiguringOutputs(mOutputSurfaces, /* detach */true);
543 
544             // Remove recording surface before calling RecordingStream.stop,
545             // since that invalidates the surface.
546             configureOutputs(mOutputSurfaces);
547 
548             mRecordingStream.stop(ctx);
549 
550             mSession.setRepeatingRequest(mRecordingRequestBuilder.build(), null, null);
551         } catch (CameraAccessException e) {
552             throw new ApiFailureException("Error stop recording", e);
553         }
554     }
555 
556     /**
557      * Flush all current requests and in-progress work
558      */
flush()559     public void flush() throws ApiFailureException {
560         minimalOpenCamera();
561         try {
562             mSession.abortCaptures();
563         } catch (CameraAccessException e) {
564             throw new ApiFailureException("Error flushing", e);
565         }
566     }
567 
getOrientationHint()568     private int getOrientationHint() {
569         // snap to {0, 90, 180, 270}
570         int orientation = ((int)Math.round(mDeviceOrientation/90.0)*90) % 360;
571 
572         CameraCharacteristics properties = getCameraCharacteristics();
573         int sensorOrientation = properties.get(CameraCharacteristics.SENSOR_ORIENTATION);
574 
575         // TODO: below calculation is for back-facing camera only
576         // front-facing camera should use:
577         // return (sensorOrientation - orientation +360) % 360;
578         return (sensorOrientation + orientation) % 360;
579     }
580 
getRecordingSize()581     private Size getRecordingSize() throws ApiFailureException {
582         try {
583             CameraCharacteristics properties =
584                     mCameraManager.getCameraCharacteristics(mCamera.getId());
585 
586             Size[] recordingSizes = null;
587             if (properties != null) {
588                 recordingSizes =
589                         properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP).
590                         getOutputSizes(MediaCodec.class);
591             }
592 
593             mEncodingBitRate = ENC_BIT_RATE_LOW;
594             if (recordingSizes == null || recordingSizes.length == 0) {
595                 Log.w(TAG, "Unable to get recording sizes, default to 640x480");
596                 return DEFAULT_SIZE;
597             } else {
598                 /**
599                  * TODO: create resolution selection widget on UI, then use the
600                  * select size. For now, return HIGH_RESOLUTION_SIZE if it
601                  * exists in the processed size list, otherwise return default
602                  * size
603                  */
604                 if (Arrays.asList(recordingSizes).contains(HIGH_RESOLUTION_SIZE)) {
605                     mEncodingBitRate = ENC_BIT_RATE_HIGH;
606                     return HIGH_RESOLUTION_SIZE;
607                 } else {
608                     // Fallback to default size when HD size is not found.
609                     Log.w(TAG,
610                             "Unable to find the requested size " + HIGH_RESOLUTION_SIZE.toString()
611                             + " Fallback to " + DEFAULT_SIZE.toString());
612                     return DEFAULT_SIZE;
613                 }
614             }
615         } catch (CameraAccessException e) {
616             throw new ApiFailureException("Error setting up video recording", e);
617         }
618     }
619 
updateCaptureRequest(CaptureRequest.Builder builder, CameraControls cameraControl)620     private void updateCaptureRequest(CaptureRequest.Builder builder,
621             CameraControls cameraControl) {
622         if (cameraControl != null) {
623             // Update the manual control metadata for capture request
624             // may disable 3A routines.
625             updateCaptureRequest(builder, cameraControl.getManualControls());
626             // Update the AF control metadata for capture request (if manual is not used)
627             updateCaptureRequest(builder, cameraControl.getAfControls());
628         }
629     }
630 
updateCaptureRequest(CaptureRequest.Builder builder, CameraManualControls manualControls)631     private void updateCaptureRequest(CaptureRequest.Builder builder,
632             CameraManualControls manualControls) {
633         if (manualControls == null) {
634             return;
635         }
636 
637         if (manualControls.isManualControlEnabled()) {
638             Log.e(TAG, "update request: " + manualControls.getSensitivity());
639             builder.set(CaptureRequest.CONTROL_MODE,
640                     CameraMetadata.CONTROL_MODE_OFF);
641             builder.set(CaptureRequest.SENSOR_SENSITIVITY,
642                     manualControls.getSensitivity());
643             builder.set(CaptureRequest.SENSOR_FRAME_DURATION,
644                     manualControls.getFrameDuration());
645             builder.set(CaptureRequest.SENSOR_EXPOSURE_TIME,
646                     manualControls.getExposure());
647 
648             if (VERBOSE) {
649                 Log.v(TAG, "updateCaptureRequest - manual - control.mode = OFF");
650             }
651         } else {
652             builder.set(CaptureRequest.CONTROL_MODE,
653                     CameraMetadata.CONTROL_MODE_AUTO);
654 
655             if (VERBOSE) {
656                 Log.v(TAG, "updateCaptureRequest - manual - control.mode = AUTO");
657             }
658         }
659     }
660 
updateCaptureRequest(CaptureRequest.Builder builder, CameraAutoFocusControls cameraAfControl)661     private void updateCaptureRequest(CaptureRequest.Builder builder,
662             CameraAutoFocusControls cameraAfControl) {
663         if (cameraAfControl == null) {
664             return;
665         }
666 
667         if (cameraAfControl.isAfControlEnabled()) {
668             builder.set(CaptureRequest.CONTROL_AF_MODE, cameraAfControl.getAfMode());
669 
670             Integer afTrigger = cameraAfControl.consumePendingTrigger();
671 
672             if (afTrigger != null) {
673                 builder.set(CaptureRequest.CONTROL_AF_TRIGGER, afTrigger);
674             }
675 
676             if (VERBOSE) {
677                 Log.v(TAG, "updateCaptureRequest - AF - set trigger to " + afTrigger);
678             }
679         }
680     }
681 
682     public interface CaptureCallback {
onCaptureAvailable(Image capture)683         void onCaptureAvailable(Image capture);
684     }
685 
686     public static abstract class CaptureResultListener
687             extends CameraCaptureSession.CaptureCallback {}
688 }
689