• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 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.example.android.camera2raw;
18 
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.app.DialogFragment;
23 import android.app.Fragment;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.graphics.ImageFormat;
27 import android.graphics.Matrix;
28 import android.graphics.RectF;
29 import android.graphics.SurfaceTexture;
30 import android.hardware.SensorManager;
31 import android.hardware.camera2.CameraAccessException;
32 import android.hardware.camera2.CameraCaptureSession;
33 import android.hardware.camera2.CameraCharacteristics;
34 import android.hardware.camera2.CameraDevice;
35 import android.hardware.camera2.CameraManager;
36 import android.hardware.camera2.CameraMetadata;
37 import android.hardware.camera2.CaptureFailure;
38 import android.hardware.camera2.CaptureRequest;
39 import android.hardware.camera2.CaptureResult;
40 import android.hardware.camera2.DngCreator;
41 import android.hardware.camera2.TotalCaptureResult;
42 import android.hardware.camera2.params.StreamConfigurationMap;
43 import android.media.Image;
44 import android.media.ImageReader;
45 import android.media.MediaScannerConnection;
46 import android.net.Uri;
47 import android.os.AsyncTask;
48 import android.os.Bundle;
49 import android.os.Environment;
50 import android.os.Handler;
51 import android.os.HandlerThread;
52 import android.os.Looper;
53 import android.os.Message;
54 import android.os.SystemClock;
55 import android.util.Log;
56 import android.util.Size;
57 import android.util.SparseIntArray;
58 import android.view.LayoutInflater;
59 import android.view.OrientationEventListener;
60 import android.view.Surface;
61 import android.view.TextureView;
62 import android.view.View;
63 import android.view.ViewGroup;
64 import android.widget.Toast;
65 
66 import java.io.File;
67 import java.io.FileOutputStream;
68 import java.io.IOException;
69 import java.io.OutputStream;
70 import java.nio.ByteBuffer;
71 import java.text.SimpleDateFormat;
72 import java.util.ArrayList;
73 import java.util.Arrays;
74 import java.util.Collections;
75 import java.util.Comparator;
76 import java.util.Date;
77 import java.util.List;
78 import java.util.Locale;
79 import java.util.Map;
80 import java.util.TreeMap;
81 import java.util.concurrent.Semaphore;
82 import java.util.concurrent.TimeUnit;
83 import java.util.concurrent.atomic.AtomicInteger;
84 
85 /**
86  * A fragment that demonstrates use of the Camera2 API to capture RAW and JPEG photos.
87  *
88  * In this example, the lifecycle of a single request to take a photo is:
89  * <ul>
90  * <li>
91  * The user presses the "Picture" button, resulting in a call to {@link #takePicture()}.
92  * </li>
93  * <li>
94  * {@link #takePicture()} initiates a pre-capture sequence that triggers the camera's built-in
95  * auto-focus, auto-exposure, and auto-white-balance algorithms (aka. "3A") to run.
96  * </li>
97  * <li>
98  * When the pre-capture sequence has finished, a {@link CaptureRequest} with a monotonically
99  * increasing request ID set by calls to {@link CaptureRequest.Builder#setTag(Object)} is sent to
100  * the camera to begin the JPEG and RAW capture sequence, and an
101  * {@link ImageSaver.ImageSaverBuilder} is stored for this request in the
102  * {@link #mJpegResultQueue} and {@link #mRawResultQueue}.
103  * </li>
104  * <li>
105  * As {@link CaptureResult}s and {@link Image}s become available via callbacks in a background
106  * thread, a {@link ImageSaver.ImageSaverBuilder} is looked up by the request ID in
107  * {@link #mJpegResultQueue} and {@link #mRawResultQueue} and updated.
108  * </li>
109  * <li>
110  * When all of the necessary results to save an image are available, the an {@link ImageSaver} is
111  * constructed by the {@link ImageSaver.ImageSaverBuilder} and passed to a separate background
112  * thread to save to a file.
113  * </li>
114  * </ul>
115  */
116 public class Camera2RawFragment extends Fragment implements View.OnClickListener {
117     /**
118      * Conversion from screen rotation to JPEG orientation.
119      */
120     private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
121 
122     static {
ORIENTATIONS.append(Surface.ROTATION_0, 0)123         ORIENTATIONS.append(Surface.ROTATION_0, 0);
ORIENTATIONS.append(Surface.ROTATION_90, 90)124         ORIENTATIONS.append(Surface.ROTATION_90, 90);
ORIENTATIONS.append(Surface.ROTATION_180, 180)125         ORIENTATIONS.append(Surface.ROTATION_180, 180);
ORIENTATIONS.append(Surface.ROTATION_270, 270)126         ORIENTATIONS.append(Surface.ROTATION_270, 270);
127     }
128 
129     /**
130      * Timeout for the pre-capture sequence.
131      */
132     private static final long PRECAPTURE_TIMEOUT_MS = 1000;
133 
134     /**
135      * Tolerance when comparing aspect ratios.
136      */
137     private static final double ASPECT_RATIO_TOLERANCE = 0.005;
138 
139     /**
140      * Tag for the {@link Log}.
141      */
142     private static final String TAG = "Camera2RawFragment";
143 
144     /**
145      * Camera state: Device is closed.
146      */
147     private static final int STATE_CLOSED = 0;
148 
149     /**
150      * Camera state: Device is opened, but is not capturing.
151      */
152     private static final int STATE_OPENED = 1;
153 
154     /**
155      * Camera state: Showing camera preview.
156      */
157     private static final int STATE_PREVIEW = 2;
158 
159     /**
160      * Camera state: Waiting for 3A convergence before capturing a photo.
161      */
162     private static final int STATE_WAITING_FOR_3A_CONVERGENCE = 3;
163 
164     /**
165      * An {@link OrientationEventListener} used to determine when device rotation has occurred.
166      * This is mainly necessary for when the device is rotated by 180 degrees, in which case
167      * onCreate or onConfigurationChanged is not called as the view dimensions remain the same,
168      * but the orientation of the has changed, and thus the preview rotation must be updated.
169      */
170     private OrientationEventListener mOrientationListener;
171 
172     /**
173      * {@link TextureView.SurfaceTextureListener} handles several lifecycle events of a
174      * {@link TextureView}.
175      */
176     private final TextureView.SurfaceTextureListener mSurfaceTextureListener
177             = new TextureView.SurfaceTextureListener() {
178 
179         @Override
180         public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
181             configureTransform(width, height);
182         }
183 
184         @Override
185         public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
186             configureTransform(width, height);
187         }
188 
189         @Override
190         public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
191             synchronized (mCameraStateLock) {
192                 mPreviewSize = null;
193             }
194             return true;
195         }
196 
197         @Override
198         public void onSurfaceTextureUpdated(SurfaceTexture texture) {
199         }
200 
201     };
202 
203     /**
204      * An {@link AutoFitTextureView} for camera preview.
205      */
206     private AutoFitTextureView mTextureView;
207 
208     /**
209      * An additional thread for running tasks that shouldn't block the UI.  This is used for all
210      * callbacks from the {@link CameraDevice} and {@link CameraCaptureSession}s.
211      */
212     private HandlerThread mBackgroundThread;
213 
214     /**
215      * A counter for tracking corresponding {@link CaptureRequest}s and {@link CaptureResult}s
216      * across the {@link CameraCaptureSession} capture callbacks.
217      */
218     private final AtomicInteger mRequestCounter = new AtomicInteger();
219 
220     /**
221      * A {@link Semaphore} to prevent the app from exiting before closing the camera.
222      */
223     private final Semaphore mCameraOpenCloseLock = new Semaphore(1);
224 
225     /**
226      * A lock protecting camera state.
227      */
228     private final Object mCameraStateLock = new Object();
229 
230     // *********************************************************************************************
231     // State protected by mCameraStateLock.
232     //
233     // The following state is used across both the UI and background threads.  Methods with "Locked"
234     // in the name expect mCameraStateLock to be held while calling.
235 
236     /**
237      * ID of the current {@link CameraDevice}.
238      */
239     private String mCameraId;
240 
241     /**
242      * A {@link CameraCaptureSession } for camera preview.
243      */
244     private CameraCaptureSession mCaptureSession;
245 
246     /**
247      * A reference to the open {@link CameraDevice}.
248      */
249     private CameraDevice mCameraDevice;
250 
251     /**
252      * The {@link Size} of camera preview.
253      */
254     private Size mPreviewSize;
255 
256     /**
257      * The {@link CameraCharacteristics} for the currently configured camera device.
258      */
259     private CameraCharacteristics mCharacteristics;
260 
261     /**
262      * A {@link Handler} for running tasks in the background.
263      */
264     private Handler mBackgroundHandler;
265 
266     /**
267      * A reference counted holder wrapping the {@link ImageReader} that handles JPEG image captures.
268      * This is used to allow us to clean up the {@link ImageReader} when all background tasks using
269      * its {@link Image}s have completed.
270      */
271     private RefCountedAutoCloseable<ImageReader> mJpegImageReader;
272 
273     /**
274      * A reference counted holder wrapping the {@link ImageReader} that handles RAW image captures.
275      * This is used to allow us to clean up the {@link ImageReader} when all background tasks using
276      * its {@link Image}s have completed.
277      */
278     private RefCountedAutoCloseable<ImageReader> mRawImageReader;
279 
280     /**
281      * Whether or not the currently configured camera device is fixed-focus.
282      */
283     private boolean mNoAFRun = false;
284 
285     /**
286      * Number of pending user requests to capture a photo.
287      */
288     private int mPendingUserCaptures = 0;
289 
290     /**
291      * Request ID to {@link ImageSaver.ImageSaverBuilder} mapping for in-progress JPEG captures.
292      */
293     private final TreeMap<Integer, ImageSaver.ImageSaverBuilder> mJpegResultQueue = new TreeMap<>();
294 
295     /**
296      * Request ID to {@link ImageSaver.ImageSaverBuilder} mapping for in-progress RAW captures.
297      */
298     private final TreeMap<Integer, ImageSaver.ImageSaverBuilder> mRawResultQueue = new TreeMap<>();
299 
300     /**
301      * {@link CaptureRequest.Builder} for the camera preview
302      */
303     private CaptureRequest.Builder mPreviewRequestBuilder;
304 
305     /**
306      * The state of the camera device.
307      *
308      * @see #mPreCaptureCallback
309      */
310     private int mState = STATE_CLOSED;
311 
312     /**
313      * Timer to use with pre-capture sequence to ensure a timely capture if 3A convergence is taking
314      * too long.
315      */
316     private long mCaptureTimer;
317 
318     //**********************************************************************************************
319 
320     /**
321      * {@link CameraDevice.StateCallback} is called when the currently active {@link CameraDevice}
322      * changes its state.
323      */
324     private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
325 
326         @Override
327         public void onOpened(CameraDevice cameraDevice) {
328             // This method is called when the camera is opened.  We start camera preview here if
329             // the TextureView displaying this has been set up.
330             synchronized (mCameraStateLock) {
331                 mState = STATE_OPENED;
332                 mCameraOpenCloseLock.release();
333                 mCameraDevice = cameraDevice;
334 
335                 // Start the preview session if the TextureView has been set up already.
336                 if (mPreviewSize != null && mTextureView.isAvailable()) {
337                     createCameraPreviewSessionLocked();
338                 }
339             }
340         }
341 
342         @Override
343         public void onDisconnected(CameraDevice cameraDevice) {
344             synchronized (mCameraStateLock) {
345                 mState = STATE_CLOSED;
346                 mCameraOpenCloseLock.release();
347                 cameraDevice.close();
348                 mCameraDevice = null;
349             }
350         }
351 
352         @Override
353         public void onError(CameraDevice cameraDevice, int error) {
354             Log.e(TAG, "Received camera device error: " + error);
355             synchronized(mCameraStateLock) {
356                 mState = STATE_CLOSED;
357                 mCameraOpenCloseLock.release();
358                 cameraDevice.close();
359                 mCameraDevice = null;
360             }
361             Activity activity = getActivity();
362             if (null != activity) {
363                 activity.finish();
364             }
365         }
366 
367     };
368 
369     /**
370      * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
371      * JPEG image is ready to be saved.
372      */
373     private final ImageReader.OnImageAvailableListener mOnJpegImageAvailableListener
374             = new ImageReader.OnImageAvailableListener() {
375 
376         @Override
377         public void onImageAvailable(ImageReader reader) {
378             dequeueAndSaveImage(mJpegResultQueue, mJpegImageReader);
379         }
380 
381     };
382 
383     /**
384      * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
385      * RAW image is ready to be saved.
386      */
387     private final ImageReader.OnImageAvailableListener mOnRawImageAvailableListener
388             = new ImageReader.OnImageAvailableListener() {
389 
390         @Override
391         public void onImageAvailable(ImageReader reader) {
392             dequeueAndSaveImage(mRawResultQueue, mRawImageReader);
393         }
394 
395     };
396 
397     /**
398      * A {@link CameraCaptureSession.CaptureCallback} that handles events for the preview and
399      * pre-capture sequence.
400      */
401     private CameraCaptureSession.CaptureCallback mPreCaptureCallback
402             = new CameraCaptureSession.CaptureCallback() {
403 
404         private void process(CaptureResult result) {
405             synchronized(mCameraStateLock) {
406                 switch (mState) {
407                     case STATE_PREVIEW: {
408                         // We have nothing to do when the camera preview is running normally.
409                         break;
410                     }
411                     case STATE_WAITING_FOR_3A_CONVERGENCE: {
412                         boolean readyToCapture = true;
413                         if (!mNoAFRun) {
414                             int afState = result.get(CaptureResult.CONTROL_AF_STATE);
415 
416                             // If auto-focus has reached locked state, we are ready to capture
417                             readyToCapture =
418                                     (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||
419                                     afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED);
420                         }
421 
422                         // If we are running on an non-legacy device, we should also wait until
423                         // auto-exposure and auto-white-balance have converged as well before
424                         // taking a picture.
425                         if (!isLegacyLocked()) {
426                             int aeState = result.get(CaptureResult.CONTROL_AE_STATE);
427                             int awbState = result.get(CaptureResult.CONTROL_AWB_STATE);
428 
429                             readyToCapture = readyToCapture &&
430                                     aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED &&
431                                     awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED;
432                         }
433 
434                         // If we haven't finished the pre-capture sequence but have hit our maximum
435                         // wait timeout, too bad! Begin capture anyway.
436                         if (!readyToCapture && hitTimeoutLocked()) {
437                             Log.w(TAG, "Timed out waiting for pre-capture sequence to complete.");
438                             readyToCapture = true;
439                         }
440 
441                         if (readyToCapture && mPendingUserCaptures > 0) {
442                             // Capture once for each user tap of the "Picture" button.
443                             while (mPendingUserCaptures > 0) {
444                                 captureStillPictureLocked();
445                                 mPendingUserCaptures--;
446                             }
447                             // After this, the camera will go back to the normal state of preview.
448                             mState = STATE_PREVIEW;
449                         }
450                     }
451                 }
452             }
453         }
454 
455         @Override
456         public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
457                                         CaptureResult partialResult) {
458             process(partialResult);
459         }
460 
461         @Override
462         public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
463                                        TotalCaptureResult result) {
464             process(result);
465         }
466 
467     };
468 
469     /**
470      * A {@link CameraCaptureSession.CaptureCallback} that handles the still JPEG and RAW capture
471      * request.
472      */
473     private final CameraCaptureSession.CaptureCallback mCaptureCallback
474             = new CameraCaptureSession.CaptureCallback() {
475         @Override
476         public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
477                                      long timestamp, long frameNumber) {
478             String currentDateTime = generateTimestamp();
479             File rawFile = new File(Environment.
480                     getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
481                     "RAW_" + currentDateTime + ".dng");
482             File jpegFile = new File(Environment.
483                     getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
484                     "JPEG_" + currentDateTime + ".jpg");
485 
486             // Look up the ImageSaverBuilder for this request and update it with the file name
487             // based on the capture start time.
488             ImageSaver.ImageSaverBuilder jpegBuilder;
489             ImageSaver.ImageSaverBuilder rawBuilder;
490             int requestId = (int) request.getTag();
491             synchronized (mCameraStateLock) {
492                 jpegBuilder = mJpegResultQueue.get(requestId);
493                 rawBuilder = mRawResultQueue.get(requestId);
494             }
495 
496             if (jpegBuilder != null) jpegBuilder.setFile(jpegFile);
497             if (rawBuilder != null) rawBuilder.setFile(rawFile);
498         }
499 
500         @Override
501         public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
502                                        TotalCaptureResult result) {
503             int requestId = (int) request.getTag();
504             ImageSaver.ImageSaverBuilder jpegBuilder;
505             ImageSaver.ImageSaverBuilder rawBuilder;
506             StringBuilder sb = new StringBuilder();
507 
508             // Look up the ImageSaverBuilder for this request and update it with the CaptureResult
509             synchronized (mCameraStateLock) {
510                 jpegBuilder = mJpegResultQueue.get(requestId);
511                 rawBuilder = mRawResultQueue.get(requestId);
512 
513                 // If we have all the results necessary, save the image to a file in the background.
514                 handleCompletionLocked(requestId, jpegBuilder, mJpegResultQueue);
515                 handleCompletionLocked(requestId, rawBuilder, mRawResultQueue);
516 
517                 if (jpegBuilder != null) {
518                     jpegBuilder.setResult(result);
519                     sb.append("Saving JPEG as: ");
520                     sb.append(jpegBuilder.getSaveLocation());
521                 }
522                 if (rawBuilder != null) {
523                     rawBuilder.setResult(result);
524                     if (jpegBuilder != null) sb.append(", ");
525                     sb.append("Saving RAW as: ");
526                     sb.append(rawBuilder.getSaveLocation());
527                 }
528                 finishedCaptureLocked();
529             }
530 
531             showToast(sb.toString());
532         }
533 
534         @Override
535         public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request,
536                                     CaptureFailure failure) {
537             int requestId = (int) request.getTag();
538             synchronized (mCameraStateLock) {
539                 mJpegResultQueue.remove(requestId);
540                 mRawResultQueue.remove(requestId);
541                 finishedCaptureLocked();
542             }
543             showToast("Capture failed!");
544         }
545 
546     };
547 
548     /**
549      * A {@link Handler} for showing {@link Toast}s on the UI thread.
550      */
551     private final Handler mMessageHandler = new Handler(Looper.getMainLooper()) {
552         @Override
553         public void handleMessage(Message msg) {
554             Activity activity = getActivity();
555             if (activity != null) {
556                 Toast.makeText(activity, (String) msg.obj, Toast.LENGTH_SHORT).show();
557             }
558         }
559     };
560 
newInstance()561     public static Camera2RawFragment newInstance() {
562         Camera2RawFragment fragment = new Camera2RawFragment();
563         fragment.setRetainInstance(true);
564         return fragment;
565     }
566 
567     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)568     public View onCreateView(LayoutInflater inflater, ViewGroup container,
569                              Bundle savedInstanceState) {
570         return inflater.inflate(R.layout.fragment_camera2_basic, container, false);
571     }
572 
573     @Override
onViewCreated(final View view, Bundle savedInstanceState)574     public void onViewCreated(final View view, Bundle savedInstanceState) {
575         view.findViewById(R.id.picture).setOnClickListener(this);
576         view.findViewById(R.id.info).setOnClickListener(this);
577         mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture);
578 
579         // Setup a new OrientationEventListener.  This is used to handle rotation events like a
580         // 180 degree rotation that do not normally trigger a call to onCreate to do view re-layout
581         // or otherwise cause the preview TextureView's size to change.
582         mOrientationListener = new OrientationEventListener(getActivity(),
583                 SensorManager.SENSOR_DELAY_NORMAL) {
584             @Override
585             public void onOrientationChanged(int orientation) {
586                 if (mTextureView != null && mTextureView.isAvailable()) {
587                     configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
588                 }
589             }
590         };
591     }
592 
593     @Override
onResume()594     public void onResume() {
595         super.onResume();
596         startBackgroundThread();
597         openCamera();
598 
599         // When the screen is turned off and turned back on, the SurfaceTexture is already
600         // available, and "onSurfaceTextureAvailable" will not be called. In that case, we should
601         // configure the preview bounds here (otherwise, we wait until the surface is ready in
602         // the SurfaceTextureListener).
603         if (mTextureView.isAvailable()) {
604             configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
605         } else {
606             mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
607         }
608         if (mOrientationListener != null && mOrientationListener.canDetectOrientation()) {
609             mOrientationListener.enable();
610         }
611     }
612 
613     @Override
onPause()614     public void onPause() {
615         if (mOrientationListener != null) {
616             mOrientationListener.disable();
617         }
618         closeCamera();
619         stopBackgroundThread();
620         super.onPause();
621     }
622 
623     @Override
onClick(View view)624     public void onClick(View view) {
625         switch (view.getId()) {
626             case R.id.picture: {
627                 takePicture();
628                 break;
629             }
630             case R.id.info: {
631                 Activity activity = getActivity();
632                 if (null != activity) {
633                     new AlertDialog.Builder(activity)
634                             .setMessage(R.string.intro_message)
635                             .setPositiveButton(android.R.string.ok, null)
636                             .show();
637                 }
638                 break;
639             }
640         }
641     }
642 
643     /**
644      * Sets up state related to camera that is needed before opening a {@link CameraDevice}.
645      */
setUpCameraOutputs()646     private boolean setUpCameraOutputs() {
647         Activity activity = getActivity();
648         CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
649         if (manager == null) {
650             ErrorDialog.buildErrorDialog("This device doesn't support Camera2 API.").
651                     show(getFragmentManager(), "dialog");
652             return false;
653         }
654         try {
655             // Find a CameraDevice that supports RAW captures, and configure state.
656             for (String cameraId : manager.getCameraIdList()) {
657                 CameraCharacteristics characteristics
658                         = manager.getCameraCharacteristics(cameraId);
659 
660                 // We only use a camera that supports RAW in this sample.
661                 if (!contains(characteristics.get(
662                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES),
663                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
664                     continue;
665                 }
666 
667                 StreamConfigurationMap map = characteristics.get(
668                         CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
669 
670                 // For still image captures, we use the largest available size.
671                 Size largestJpeg = Collections.max(
672                         Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
673                         new CompareSizesByArea());
674 
675                 Size largestRaw = Collections.max(
676                         Arrays.asList(map.getOutputSizes(ImageFormat.RAW_SENSOR)),
677                         new CompareSizesByArea());
678 
679                 synchronized(mCameraStateLock) {
680                     // Set up ImageReaders for JPEG and RAW outputs.  Place these in a reference
681                     // counted wrapper to ensure they are only closed when all background tasks
682                     // using them are finished.
683                     if (mJpegImageReader == null || mJpegImageReader.getAndRetain() == null) {
684                         mJpegImageReader = new RefCountedAutoCloseable<>(
685                                 ImageReader.newInstance(largestJpeg.getWidth(),
686                                 largestJpeg.getHeight(), ImageFormat.JPEG, /*maxImages*/5));
687                     }
688                     mJpegImageReader.get().setOnImageAvailableListener(
689                             mOnJpegImageAvailableListener, mBackgroundHandler);
690 
691                     if (mRawImageReader == null || mRawImageReader.getAndRetain() == null) {
692                         mRawImageReader = new RefCountedAutoCloseable<>(
693                                 ImageReader.newInstance(largestRaw.getWidth(),
694                                 largestRaw.getHeight(), ImageFormat.RAW_SENSOR, /*maxImages*/ 5));
695                     }
696                     mRawImageReader.get().setOnImageAvailableListener(
697                             mOnRawImageAvailableListener, mBackgroundHandler);
698 
699                     mCharacteristics = characteristics;
700                     mCameraId = cameraId;
701                 }
702                 return true;
703             }
704         } catch (CameraAccessException e) {
705             e.printStackTrace();
706         }
707 
708         // If we found no suitable cameras for capturing RAW, warn the user.
709         ErrorDialog.buildErrorDialog("This device doesn't support capturing RAW photos").
710                 show(getFragmentManager(), "dialog");
711         return false;
712     }
713 
714     /**
715      * Opens the camera specified by {@link #mCameraId}.
716      */
openCamera()717     private void openCamera() {
718         if (!setUpCameraOutputs()) {
719             return;
720         }
721 
722         Activity activity = getActivity();
723         CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
724         try {
725             // Wait for any previously running session to finish.
726             if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
727                 throw new RuntimeException("Time out waiting to lock camera opening.");
728             }
729 
730             String cameraId;
731             Handler backgroundHandler;
732             synchronized (mCameraStateLock) {
733                 cameraId = mCameraId;
734                 backgroundHandler = mBackgroundHandler;
735             }
736 
737             // Attempt to open the camera. mStateCallback will be called on the background handler's
738             // thread when this succeeds or fails.
739             manager.openCamera(cameraId, mStateCallback, backgroundHandler);
740         } catch (CameraAccessException e) {
741             e.printStackTrace();
742         } catch (InterruptedException e) {
743             throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
744         }
745     }
746 
747     /**
748      * Closes the current {@link CameraDevice}.
749      */
closeCamera()750     private void closeCamera() {
751         try {
752             mCameraOpenCloseLock.acquire();
753             synchronized(mCameraStateLock) {
754 
755                 // Reset state and clean up resources used by the camera.
756                 // Note: After calling this, the ImageReaders will be closed after any background
757                 // tasks saving Images from these readers have been completed.
758                 mPendingUserCaptures = 0;
759                 mState = STATE_CLOSED;
760                 if (null != mCaptureSession) {
761                     mCaptureSession.close();
762                     mCaptureSession = null;
763                 }
764                 if (null != mCameraDevice) {
765                     mCameraDevice.close();
766                     mCameraDevice = null;
767                 }
768                 if (null != mJpegImageReader) {
769                     mJpegImageReader.close();
770                     mJpegImageReader = null;
771                 }
772                 if (null != mRawImageReader) {
773                     mRawImageReader.close();
774                     mRawImageReader = null;
775                 }
776             }
777         } catch (InterruptedException e) {
778             throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
779         } finally {
780             mCameraOpenCloseLock.release();
781         }
782     }
783 
784     /**
785      * Starts a background thread and its {@link Handler}.
786      */
startBackgroundThread()787     private void startBackgroundThread() {
788         mBackgroundThread = new HandlerThread("CameraBackground");
789         mBackgroundThread.start();
790         synchronized(mCameraStateLock) {
791             mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
792         }
793     }
794 
795     /**
796      * Stops the background thread and its {@link Handler}.
797      */
stopBackgroundThread()798     private void stopBackgroundThread() {
799         mBackgroundThread.quitSafely();
800         try {
801             mBackgroundThread.join();
802             mBackgroundThread = null;
803             synchronized (mCameraStateLock) {
804                 mBackgroundHandler = null;
805             }
806         } catch (InterruptedException e) {
807             e.printStackTrace();
808         }
809     }
810 
811     /**
812      * Creates a new {@link CameraCaptureSession} for camera preview.
813      *
814      * Call this only with {@link #mCameraStateLock} held.
815      */
createCameraPreviewSessionLocked()816     private void createCameraPreviewSessionLocked() {
817         try {
818             SurfaceTexture texture = mTextureView.getSurfaceTexture();
819             // We configure the size of default buffer to be the size of camera preview we want.
820             texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
821 
822             // This is the output Surface we need to start preview.
823             Surface surface = new Surface(texture);
824 
825             // We set up a CaptureRequest.Builder with the output Surface.
826             mPreviewRequestBuilder
827                     = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
828             mPreviewRequestBuilder.addTarget(surface);
829 
830             // Here, we create a CameraCaptureSession for camera preview.
831             mCameraDevice.createCaptureSession(Arrays.asList(surface,
832                     mJpegImageReader.get().getSurface(),
833                     mRawImageReader.get().getSurface()), new CameraCaptureSession.StateCallback() {
834                         @Override
835                         public void onConfigured(CameraCaptureSession cameraCaptureSession) {
836                             synchronized (mCameraStateLock) {
837                                 // The camera is already closed
838                                 if (null == mCameraDevice) {
839                                     return;
840                                 }
841 
842                                 try {
843                                     setup3AControlsLocked(mPreviewRequestBuilder);
844                                     // Finally, we start displaying the camera preview.
845                                     cameraCaptureSession.setRepeatingRequest(
846                                             mPreviewRequestBuilder.build(),
847                                             mPreCaptureCallback, mBackgroundHandler);
848                                     mState = STATE_PREVIEW;
849                                 } catch (CameraAccessException|IllegalStateException e) {
850                                     e.printStackTrace();
851                                     return;
852                                 }
853                                 // When the session is ready, we start displaying the preview.
854                                 mCaptureSession = cameraCaptureSession;
855                             }
856                         }
857 
858                         @Override
859                         public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
860                             showToast("Failed to configure camera.");
861                         }
862                     }, mBackgroundHandler
863             );
864         } catch (CameraAccessException e) {
865             e.printStackTrace();
866         }
867     }
868 
869     /**
870      * Configure the given {@link CaptureRequest.Builder} to use auto-focus, auto-exposure, and
871      * auto-white-balance controls if available.
872      *
873      * Call this only with {@link #mCameraStateLock} held.
874      *
875      * @param builder the builder to configure.
876      */
setup3AControlsLocked(CaptureRequest.Builder builder)877     private void setup3AControlsLocked(CaptureRequest.Builder builder) {
878         // Enable auto-magical 3A run by camera device
879         builder.set(CaptureRequest.CONTROL_MODE,
880                 CaptureRequest.CONTROL_MODE_AUTO);
881 
882         Float minFocusDist =
883                 mCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE);
884 
885         // If MINIMUM_FOCUS_DISTANCE is 0, lens is fixed-focus and we need to skip the AF run.
886         mNoAFRun = (minFocusDist == null || minFocusDist == 0);
887 
888         if (!mNoAFRun) {
889             // If there is a "continuous picture" mode available, use it, otherwise default to AUTO.
890             if (contains(mCharacteristics.get(
891                             CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES),
892                     CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)) {
893                 builder.set(CaptureRequest.CONTROL_AF_MODE,
894                         CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
895             } else {
896                 builder.set(CaptureRequest.CONTROL_AF_MODE,
897                         CaptureRequest.CONTROL_AF_MODE_AUTO);
898             }
899         }
900 
901         // If there is an auto-magical flash control mode available, use it, otherwise default to
902         // the "on" mode, which is guaranteed to always be available.
903         if (contains(mCharacteristics.get(
904                         CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES),
905                 CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)) {
906             builder.set(CaptureRequest.CONTROL_AE_MODE,
907                     CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
908         } else {
909             builder.set(CaptureRequest.CONTROL_AE_MODE,
910                     CaptureRequest.CONTROL_AE_MODE_ON);
911         }
912 
913         // If there is an auto-magical white balance control mode available, use it.
914         if (contains(mCharacteristics.get(
915                         CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES),
916                 CaptureRequest.CONTROL_AWB_MODE_AUTO)) {
917             // Allow AWB to run auto-magically if this device supports this
918             builder.set(CaptureRequest.CONTROL_AWB_MODE,
919                     CaptureRequest.CONTROL_AWB_MODE_AUTO);
920         }
921     }
922 
923     /**
924      * Configure the necessary {@link android.graphics.Matrix} transformation to `mTextureView`,
925      * and start/restart the preview capture session if necessary.
926      *
927      * This method should be called after the camera state has been initialized in
928      * setUpCameraOutputs.
929      *
930      * @param viewWidth  The width of `mTextureView`
931      * @param viewHeight The height of `mTextureView`
932      */
configureTransform(int viewWidth, int viewHeight)933     private void configureTransform(int viewWidth, int viewHeight) {
934         Activity activity = getActivity();
935         synchronized(mCameraStateLock) {
936             if (null == mTextureView || null == activity) {
937                 return;
938             }
939 
940             StreamConfigurationMap map = mCharacteristics.get(
941                     CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
942 
943             // For still image captures, we always use the largest available size.
944             Size largestJpeg = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
945                     new CompareSizesByArea());
946 
947             // Find the rotation of the device relative to the native device orientation.
948             int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
949 
950             // Find the rotation of the device relative to the camera sensor's orientation.
951             int totalRotation = sensorToDeviceRotation(mCharacteristics, deviceRotation);
952 
953             // Swap the view dimensions for calculation as needed if they are rotated relative to
954             // the sensor.
955             boolean swappedDimensions = totalRotation == 90 || totalRotation == 270;
956             int rotatedViewWidth = viewWidth;
957             int rotatedViewHeight = viewHeight;
958             if (swappedDimensions) {
959                 rotatedViewWidth = viewHeight;
960                 rotatedViewHeight = viewWidth;
961             }
962 
963             // Find the best preview size for these view dimensions and configured JPEG size.
964             Size previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
965                     rotatedViewWidth, rotatedViewHeight, largestJpeg);
966 
967             if (swappedDimensions) {
968                 mTextureView.setAspectRatio(
969                         previewSize.getHeight(), previewSize.getWidth());
970             } else {
971                 mTextureView.setAspectRatio(
972                         previewSize.getWidth(), previewSize.getHeight());
973             }
974 
975             // Find rotation of device in degrees (reverse device orientation for front-facing
976             // cameras).
977             int rotation = (mCharacteristics.get(CameraCharacteristics.LENS_FACING) ==
978                     CameraCharacteristics.LENS_FACING_FRONT) ?
979                     (360 + ORIENTATIONS.get(deviceRotation)) % 360 :
980                     (360 - ORIENTATIONS.get(deviceRotation)) % 360;
981 
982             Matrix matrix = new Matrix();
983             RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
984             RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth());
985             float centerX = viewRect.centerX();
986             float centerY = viewRect.centerY();
987 
988             // Initially, output stream images from the Camera2 API will be rotated to the native
989             // device orientation from the sensor's orientation, and the TextureView will default to
990             // scaling these buffers to fill it's view bounds.  If the aspect ratios and relative
991             // orientations are correct, this is fine.
992             //
993             // However, if the device orientation has been rotated relative to its native
994             // orientation so that the TextureView's dimensions are swapped relative to the
995             // native device orientation, we must do the following to ensure the output stream
996             // images are not incorrectly scaled by the TextureView:
997             //   - Undo the scale-to-fill from the output buffer's dimensions (i.e. its dimensions
998             //     in the native device orientation) to the TextureView's dimension.
999             //   - Apply a scale-to-fill from the output buffer's rotated dimensions
1000             //     (i.e. its dimensions in the current device orientation) to the TextureView's
1001             //     dimensions.
1002             //   - Apply the rotation from the native device orientation to the current device
1003             //     rotation.
1004             if (Surface.ROTATION_90 == deviceRotation || Surface.ROTATION_270 == deviceRotation) {
1005                 bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
1006                 matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
1007                 float scale = Math.max(
1008                         (float) viewHeight / previewSize.getHeight(),
1009                         (float) viewWidth / previewSize.getWidth());
1010                 matrix.postScale(scale, scale, centerX, centerY);
1011 
1012             }
1013             matrix.postRotate(rotation, centerX, centerY);
1014 
1015             mTextureView.setTransform(matrix);
1016 
1017             // Start or restart the active capture session if the preview was initialized or
1018             // if its aspect ratio changed significantly.
1019             if (mPreviewSize == null || !checkAspectsEqual(previewSize, mPreviewSize)) {
1020                 mPreviewSize = previewSize;
1021                 if (mState != STATE_CLOSED) {
1022                     createCameraPreviewSessionLocked();
1023                 }
1024             }
1025         }
1026     }
1027 
1028     /**
1029      * Initiate a still image capture.
1030      *
1031      * This function sends a capture request that initiates a pre-capture sequence in our state
1032      * machine that waits for auto-focus to finish, ending in a "locked" state where the lens is no
1033      * longer moving, waits for auto-exposure to choose a good exposure value, and waits for
1034      * auto-white-balance to converge.
1035      */
takePicture()1036     private void takePicture() {
1037         synchronized(mCameraStateLock) {
1038             mPendingUserCaptures++;
1039 
1040             // If we already triggered a pre-capture sequence, or are in a state where we cannot
1041             // do this, return immediately.
1042             if (mState != STATE_PREVIEW) {
1043                 return;
1044             }
1045 
1046             try {
1047                 // Trigger an auto-focus run if camera is capable. If the camera is already focused,
1048                 // this should do nothing.
1049                 if (!mNoAFRun) {
1050                     mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
1051                             CameraMetadata.CONTROL_AF_TRIGGER_START);
1052                 }
1053 
1054                 // If this is not a legacy device, we can also trigger an auto-exposure metering
1055                 // run.
1056                 if (!isLegacyLocked()) {
1057                     // Tell the camera to lock focus.
1058                     mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
1059                             CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START);
1060                 }
1061 
1062                 // Update state machine to wait for auto-focus, auto-exposure, and
1063                 // auto-white-balance (aka. "3A") to converge.
1064                 mState = STATE_WAITING_FOR_3A_CONVERGENCE;
1065 
1066                 // Start a timer for the pre-capture sequence.
1067                 startTimerLocked();
1068 
1069                 // Replace the existing repeating request with one with updated 3A triggers.
1070                 mCaptureSession.capture(mPreviewRequestBuilder.build(), mPreCaptureCallback,
1071                         mBackgroundHandler);
1072             } catch (CameraAccessException e) {
1073                 e.printStackTrace();
1074             }
1075         }
1076     }
1077 
1078     /**
1079      * Send a capture request to the camera device that initiates a capture targeting the JPEG and
1080      * RAW outputs.
1081      *
1082      * Call this only with {@link #mCameraStateLock} held.
1083      */
captureStillPictureLocked()1084     private void captureStillPictureLocked() {
1085         try {
1086             final Activity activity = getActivity();
1087             if (null == activity || null == mCameraDevice) {
1088                 return;
1089             }
1090             // This is the CaptureRequest.Builder that we use to take a picture.
1091             final CaptureRequest.Builder captureBuilder =
1092                     mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
1093 
1094             captureBuilder.addTarget(mJpegImageReader.get().getSurface());
1095             captureBuilder.addTarget(mRawImageReader.get().getSurface());
1096 
1097             // Use the same AE and AF modes as the preview.
1098             setup3AControlsLocked(captureBuilder);
1099 
1100             // Set orientation.
1101             int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
1102             captureBuilder.set(CaptureRequest.JPEG_ORIENTATION,
1103                     sensorToDeviceRotation(mCharacteristics, rotation));
1104 
1105             // Set request tag to easily track results in callbacks.
1106             captureBuilder.setTag(mRequestCounter.getAndIncrement());
1107 
1108             CaptureRequest request = captureBuilder.build();
1109 
1110             // Create an ImageSaverBuilder in which to collect results, and add it to the queue
1111             // of active requests.
1112             ImageSaver.ImageSaverBuilder jpegBuilder = new ImageSaver.ImageSaverBuilder(activity)
1113                     .setCharacteristics(mCharacteristics);
1114             ImageSaver.ImageSaverBuilder rawBuilder = new ImageSaver.ImageSaverBuilder(activity)
1115                     .setCharacteristics(mCharacteristics);
1116 
1117             mJpegResultQueue.put((int) request.getTag(), jpegBuilder);
1118             mRawResultQueue.put((int) request.getTag(), rawBuilder);
1119 
1120             mCaptureSession.capture(request, mCaptureCallback, mBackgroundHandler);
1121 
1122         } catch (CameraAccessException e) {
1123             e.printStackTrace();
1124         }
1125     }
1126 
1127     /**
1128      * Called after a RAW/JPEG capture has completed; resets the AF trigger state for the
1129      * pre-capture sequence.
1130      *
1131      * Call this only with {@link #mCameraStateLock} held.
1132      */
finishedCaptureLocked()1133     private void finishedCaptureLocked() {
1134         try {
1135             // Reset the auto-focus trigger in case AF didn't run quickly enough.
1136             if (!mNoAFRun) {
1137                 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
1138                         CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
1139 
1140                 mCaptureSession.capture(mPreviewRequestBuilder.build(), mPreCaptureCallback,
1141                         mBackgroundHandler);
1142 
1143                 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
1144                         CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
1145             }
1146         } catch (CameraAccessException e) {
1147             e.printStackTrace();
1148         }
1149     }
1150 
1151     /**
1152      * Retrieve the next {@link Image} from a reference counted {@link ImageReader}, retaining
1153      * that {@link ImageReader} until that {@link Image} is no longer in use, and set this
1154      * {@link Image} as the result for the next request in the queue of pending requests.  If
1155      * all necessary information is available, begin saving the image to a file in a background
1156      * thread.
1157      *
1158      * @param pendingQueue the currently active requests.
1159      * @param reader a reference counted wrapper containing an {@link ImageReader} from which to
1160      *               acquire an image.
1161      */
dequeueAndSaveImage(TreeMap<Integer, ImageSaver.ImageSaverBuilder> pendingQueue, RefCountedAutoCloseable<ImageReader> reader)1162     private void dequeueAndSaveImage(TreeMap<Integer, ImageSaver.ImageSaverBuilder> pendingQueue,
1163                                      RefCountedAutoCloseable<ImageReader> reader) {
1164         synchronized (mCameraStateLock) {
1165             Map.Entry<Integer, ImageSaver.ImageSaverBuilder> entry =
1166                     pendingQueue.firstEntry();
1167             ImageSaver.ImageSaverBuilder builder = entry.getValue();
1168 
1169             // Increment reference count to prevent ImageReader from being closed while we
1170             // are saving its Images in a background thread (otherwise their resources may
1171             // be freed while we are writing to a file).
1172             if (reader == null || reader.getAndRetain() == null) {
1173                 Log.e(TAG, "Paused the activity before we could save the image," +
1174                         " ImageReader already closed.");
1175                 pendingQueue.remove(entry.getKey());
1176                 return;
1177             }
1178 
1179             Image image;
1180             try {
1181                 image = reader.get().acquireNextImage();
1182             } catch (IllegalStateException e) {
1183                 Log.e(TAG, "Too many images queued for saving, dropping image for request: " +
1184                         entry.getKey());
1185                 pendingQueue.remove(entry.getKey());
1186                 return;
1187             }
1188 
1189             builder.setRefCountedReader(reader).setImage(image);
1190 
1191             handleCompletionLocked(entry.getKey(), builder, pendingQueue);
1192         }
1193     }
1194 
1195     /**
1196      * Runnable that saves an {@link Image} into the specified {@link File}, and updates
1197      * {@link android.provider.MediaStore} to include the resulting file.
1198      *
1199      * This can be constructed through an {@link ImageSaverBuilder} as the necessary image and
1200      * result information becomes available.
1201      */
1202     private static class ImageSaver implements Runnable {
1203 
1204         /**
1205          * The image to save.
1206          */
1207         private final Image mImage;
1208         /**
1209          * The file we save the image into.
1210          */
1211         private final File mFile;
1212 
1213         /**
1214          * The CaptureResult for this image capture.
1215          */
1216         private final CaptureResult mCaptureResult;
1217 
1218         /**
1219          * The CameraCharacteristics for this camera device.
1220          */
1221         private final CameraCharacteristics mCharacteristics;
1222 
1223         /**
1224          * The Context to use when updating MediaStore with the saved images.
1225          */
1226         private final Context mContext;
1227 
1228         /**
1229          * A reference counted wrapper for the ImageReader that owns the given image.
1230          */
1231         private final RefCountedAutoCloseable<ImageReader> mReader;
1232 
ImageSaver(Image image, File file, CaptureResult result, CameraCharacteristics characteristics, Context context, RefCountedAutoCloseable<ImageReader> reader)1233         private ImageSaver(Image image, File file, CaptureResult result,
1234                 CameraCharacteristics characteristics, Context context,
1235                 RefCountedAutoCloseable<ImageReader> reader) {
1236             mImage = image;
1237             mFile = file;
1238             mCaptureResult = result;
1239             mCharacteristics = characteristics;
1240             mContext = context;
1241             mReader = reader;
1242         }
1243 
1244         @Override
run()1245         public void run() {
1246             boolean success = false;
1247             int format = mImage.getFormat();
1248             switch(format) {
1249                 case ImageFormat.JPEG: {
1250                     ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
1251                     byte[] bytes = new byte[buffer.remaining()];
1252                     buffer.get(bytes);
1253                     FileOutputStream output = null;
1254                     try {
1255                         output = new FileOutputStream(mFile);
1256                         output.write(bytes);
1257                         success = true;
1258                     } catch (IOException e) {
1259                         e.printStackTrace();
1260                     } finally {
1261                         mImage.close();
1262                         closeOutput(output);
1263                     }
1264                     break;
1265                 }
1266                 case ImageFormat.RAW_SENSOR: {
1267                     DngCreator dngCreator = new DngCreator(mCharacteristics, mCaptureResult);
1268                     FileOutputStream output = null;
1269                     try {
1270                         output = new FileOutputStream(mFile);
1271                         dngCreator.writeImage(output, mImage);
1272                         success = true;
1273                     } catch (IOException e) {
1274                         e.printStackTrace();
1275                     } finally {
1276                         mImage.close();
1277                         closeOutput(output);
1278                     }
1279                     break;
1280                 }
1281                 default: {
1282                     Log.e(TAG, "Cannot save image, unexpected image format:" + format);
1283                     break;
1284                 }
1285             }
1286 
1287             // Decrement reference count to allow ImageReader to be closed to free up resources.
1288             mReader.close();
1289 
1290             // If saving the file succeeded, update MediaStore.
1291             if (success) {
1292                 MediaScannerConnection.scanFile(mContext, new String[] { mFile.getPath()},
1293                 /*mimeTypes*/null, new MediaScannerConnection.MediaScannerConnectionClient() {
1294                     @Override
1295                     public void onMediaScannerConnected() {
1296                         // Do nothing
1297                     }
1298 
1299                     @Override
1300                     public void onScanCompleted(String path, Uri uri) {
1301                         Log.i(TAG, "Scanned " + path + ":");
1302                         Log.i(TAG, "-> uri=" + uri);
1303                     }
1304                 });
1305             }
1306         }
1307 
1308         /**
1309          * Builder class for constructing {@link ImageSaver}s.
1310          *
1311          * This class is thread safe.
1312          */
1313         public static class ImageSaverBuilder {
1314             private Image mImage;
1315             private File mFile;
1316             private CaptureResult mCaptureResult;
1317             private CameraCharacteristics mCharacteristics;
1318             private Context mContext;
1319             private RefCountedAutoCloseable<ImageReader> mReader;
1320 
1321             /**
1322              * Construct a new ImageSaverBuilder using the given {@link Context}.
1323              * @param context a {@link Context} to for accessing the
1324              *                  {@link android.provider.MediaStore}.
1325              */
ImageSaverBuilder(final Context context)1326             public ImageSaverBuilder(final Context context) {
1327                 mContext = context;
1328             }
1329 
setRefCountedReader( RefCountedAutoCloseable<ImageReader> reader)1330             public synchronized ImageSaverBuilder setRefCountedReader(
1331                     RefCountedAutoCloseable<ImageReader> reader) {
1332                 if (reader == null ) throw new NullPointerException();
1333 
1334                 mReader = reader;
1335                 return this;
1336             }
1337 
setImage(final Image image)1338             public synchronized ImageSaverBuilder setImage(final Image image) {
1339                 if (image == null) throw new NullPointerException();
1340                 mImage = image;
1341                 return this;
1342             }
1343 
setFile(final File file)1344             public synchronized ImageSaverBuilder setFile(final File file) {
1345                 if (file == null) throw new NullPointerException();
1346                 mFile = file;
1347                 return this;
1348             }
1349 
setResult(final CaptureResult result)1350             public synchronized ImageSaverBuilder setResult(final CaptureResult result) {
1351                 if (result == null) throw new NullPointerException();
1352                 mCaptureResult = result;
1353                 return this;
1354             }
1355 
setCharacteristics( final CameraCharacteristics characteristics)1356             public synchronized ImageSaverBuilder setCharacteristics(
1357                     final CameraCharacteristics characteristics) {
1358                 if (characteristics == null) throw new NullPointerException();
1359                 mCharacteristics = characteristics;
1360                 return this;
1361             }
1362 
buildIfComplete()1363             public synchronized ImageSaver buildIfComplete() {
1364                 if (!isComplete()) {
1365                     return null;
1366                 }
1367                 return new ImageSaver(mImage, mFile, mCaptureResult, mCharacteristics, mContext,
1368                         mReader);
1369             }
1370 
getSaveLocation()1371             public synchronized String getSaveLocation() {
1372                 return (mFile == null) ? "Unknown" : mFile.toString();
1373             }
1374 
isComplete()1375             private boolean isComplete() {
1376                 return mImage != null && mFile != null && mCaptureResult != null
1377                         && mCharacteristics != null;
1378             }
1379         }
1380     }
1381 
1382     // Utility classes and methods:
1383     // *********************************************************************************************
1384 
1385     /**
1386      * Comparator based on area of the given {@link Size} objects.
1387      */
1388     static class CompareSizesByArea implements Comparator<Size> {
1389 
1390         @Override
compare(Size lhs, Size rhs)1391         public int compare(Size lhs, Size rhs) {
1392             // We cast here to ensure the multiplications won't overflow
1393             return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
1394                     (long) rhs.getWidth() * rhs.getHeight());
1395         }
1396 
1397     }
1398 
1399     /**
1400      * A dialog fragment for displaying non-recoverable errors; this {@ling Activity} will be
1401      * finished once the dialog has been acknowledged by the user.
1402      */
1403     public static class ErrorDialog extends DialogFragment {
1404 
1405         private String mErrorMessage;
1406 
ErrorDialog()1407         public ErrorDialog() {
1408             mErrorMessage = "Unknown error occurred!";
1409         }
1410 
1411         // Build a dialog with a custom message (Fragments require default constructor).
buildErrorDialog(String errorMessage)1412         public static ErrorDialog buildErrorDialog(String errorMessage) {
1413             ErrorDialog dialog = new ErrorDialog();
1414             dialog.mErrorMessage = errorMessage;
1415             return dialog;
1416         }
1417 
1418         @Override
onCreateDialog(Bundle savedInstanceState)1419         public Dialog onCreateDialog(Bundle savedInstanceState) {
1420             final Activity activity = getActivity();
1421             return new AlertDialog.Builder(activity)
1422                     .setMessage(mErrorMessage)
1423                     .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1424                         @Override
1425                         public void onClick(DialogInterface dialogInterface, int i) {
1426                             activity.finish();
1427                         }
1428                     })
1429                     .create();
1430         }
1431     }
1432 
1433     /**
1434      * A wrapper for an {@link AutoCloseable} object that implements reference counting to allow
1435      * for resource management.
1436      */
1437     public static class RefCountedAutoCloseable<T extends AutoCloseable> implements AutoCloseable {
1438         private T mObject;
1439         private long mRefCount = 0;
1440 
1441         /**
1442          * Wrap the given object.
1443          * @param object an object to wrap.
1444          */
1445         public RefCountedAutoCloseable(T object) {
1446             if (object == null) throw new NullPointerException();
1447             mObject = object;
1448         }
1449 
1450         /**
1451          * Increment the reference count and return the wrapped object.
1452          *
1453          * @return the wrapped object, or null if the object has been released.
1454          */
1455         public synchronized T getAndRetain() {
1456             if (mRefCount < 0) {
1457                 return null;
1458             }
1459             mRefCount++;
1460             return mObject;
1461         }
1462 
1463         /**
1464          * Return the wrapped object.
1465          *
1466          * @return the wrapped object, or null if the object has been released.
1467          */
1468         public synchronized T get() {
1469             return mObject;
1470         }
1471 
1472         /**
1473          * Decrement the reference count and release the wrapped object if there are no other
1474          * users retaining this object.
1475          */
1476         @Override
1477         public synchronized void close() {
1478             if (mRefCount >= 0) {
1479                 mRefCount--;
1480                 if (mRefCount < 0) {
1481                     try {
1482                         mObject.close();
1483                     } catch (Exception e) {
1484                         throw new RuntimeException(e);
1485                     } finally {
1486                         mObject = null;
1487                     }
1488                 }
1489             }
1490         }
1491     }
1492 
1493     /**
1494      * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
1495      * width and height are at least as large as the respective requested values, and whose aspect
1496      * ratio matches with the specified value.
1497      *
1498      * @param choices     The list of sizes that the camera supports for the intended output class
1499      * @param width       The minimum desired width
1500      * @param height      The minimum desired height
1501      * @param aspectRatio The aspect ratio
1502      * @return The optimal {@code Size}, or an arbitrary one if none were big enough
1503      */
1504     private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
1505         // Collect the supported resolutions that are at least as big as the preview Surface
1506         List<Size> bigEnough = new ArrayList<>();
1507         int w = aspectRatio.getWidth();
1508         int h = aspectRatio.getHeight();
1509         for (Size option : choices) {
1510             if (option.getHeight() == option.getWidth() * h / w &&
1511                     option.getWidth() >= width && option.getHeight() >= height) {
1512                 bigEnough.add(option);
1513             }
1514         }
1515 
1516         // Pick the smallest of those, assuming we found any
1517         if (bigEnough.size() > 0) {
1518             return Collections.min(bigEnough, new CompareSizesByArea());
1519         } else {
1520             Log.e(TAG, "Couldn't find any suitable preview size");
1521             return choices[0];
1522         }
1523     }
1524 
1525     /**
1526      * Generate a string containing a formatted timestamp with the current date and time.
1527      *
1528      * @return a {@link String} representing a time.
1529      */
1530     private static String generateTimestamp() {
1531         SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SSS", Locale.US);
1532         return sdf.format(new Date());
1533     }
1534 
1535     /**
1536      * Cleanup the given {@link OutputStream}.
1537      *
1538      * @param outputStream the stream to close.
1539      */
1540     private static void closeOutput(OutputStream outputStream) {
1541         if (null != outputStream) {
1542             try {
1543                 outputStream.close();
1544             } catch (IOException e) {
1545                 e.printStackTrace();
1546             }
1547         }
1548     }
1549 
1550     /**
1551      * Return true if the given array contains the given integer.
1552      *
1553      * @param modes array to check.
1554      * @param mode integer to get for.
1555      * @return true if the array contains the given integer, otherwise false.
1556      */
1557     private static boolean contains(int[] modes, int mode) {
1558         if (modes == null) {
1559             return false;
1560         }
1561         for (int i : modes) {
1562             if (i == mode) {
1563                 return true;
1564             }
1565         }
1566         return false;
1567     }
1568 
1569     /**
1570      * Return true if the two given {@link Size}s have the same aspect ratio.
1571      *
1572      * @param a first {@link Size} to compare.
1573      * @param b second {@link Size} to compare.
1574      * @return true if the sizes have the same aspect ratio, otherwise false.
1575      */
1576     private static boolean checkAspectsEqual(Size a, Size b) {
1577         double aAspect = a.getWidth() / (double) a.getHeight();
1578         double bAspect = b.getWidth() / (double) b.getHeight();
1579         return Math.abs(aAspect - bAspect) <= ASPECT_RATIO_TOLERANCE;
1580     }
1581 
1582     /**
1583      * Rotation need to transform from the camera sensor orientation to the device's current
1584      * orientation.
1585      * @param c the {@link CameraCharacteristics} to query for the camera sensor orientation.
1586      * @param deviceOrientation the current device orientation relative to the native device
1587      *                          orientation.
1588      * @return the total rotation from the sensor orientation to the current device orientation.
1589      */
1590     private static int sensorToDeviceRotation(CameraCharacteristics c, int deviceOrientation) {
1591         int sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);
1592 
1593         // Get device orientation in degrees
1594         deviceOrientation = ORIENTATIONS.get(deviceOrientation);
1595 
1596         // Reverse device orientation for front-facing cameras
1597         if (c.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
1598             deviceOrientation = -deviceOrientation;
1599         }
1600 
1601         // Calculate desired JPEG orientation relative to camera orientation to make
1602         // the image upright relative to the device orientation
1603         return (sensorOrientation + deviceOrientation + 360) % 360;
1604     }
1605 
1606     /**
1607      * Shows a {@link Toast} on the UI thread.
1608      *
1609      * @param text The message to show.
1610      */
1611     private void showToast(String text) {
1612         // We show a Toast by sending request message to mMessageHandler. This makes sure that the
1613         // Toast is shown on the UI thread.
1614         Message message = Message.obtain();
1615         message.obj = text;
1616         mMessageHandler.sendMessage(message);
1617     }
1618 
1619     /**
1620      * If the given request has been completed, remove it from the queue of active requests and
1621      * send an {@link ImageSaver} with the results from this request to a background thread to
1622      * save a file.
1623      *
1624      * Call this only with {@link #mCameraStateLock} held.
1625      *
1626      * @param requestId the ID of the {@link CaptureRequest} to handle.
1627      * @param builder the {@link ImageSaver.ImageSaverBuilder} for this request.
1628      * @param queue the queue to remove this request from, if completed.
1629      */
1630     private void handleCompletionLocked(int requestId, ImageSaver.ImageSaverBuilder builder,
1631                                         TreeMap<Integer, ImageSaver.ImageSaverBuilder> queue) {
1632         if (builder == null) return;
1633         ImageSaver saver = builder.buildIfComplete();
1634         if (saver != null) {
1635             queue.remove(requestId);
1636             AsyncTask.THREAD_POOL_EXECUTOR.execute(saver);
1637         }
1638     }
1639 
1640     /**
1641      * Check if we are using a device that only supports the LEGACY hardware level.
1642      *
1643      * Call this only with {@link #mCameraStateLock} held.
1644      *
1645      * @return true if this is a legacy device.
1646      */
1647     private boolean isLegacyLocked() {
1648         return mCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
1649                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
1650     }
1651 
1652     /**
1653      * Start the timer for the pre-capture sequence.
1654      *
1655      * Call this only with {@link #mCameraStateLock} held.
1656      */
1657     private void startTimerLocked() {
1658         mCaptureTimer = SystemClock.elapsedRealtime();
1659     }
1660 
1661     /**
1662      * Check if the timer for the pre-capture sequence has been hit.
1663      *
1664      * Call this only with {@link #mCameraStateLock} held.
1665      *
1666      * @return true if the timeout occurred.
1667      */
1668     private boolean hitTimeoutLocked() {
1669         return (SystemClock.elapsedRealtime() - mCaptureTimer) > PRECAPTURE_TIMEOUT_MS;
1670     }
1671 
1672     // *********************************************************************************************
1673 
1674 }
1675