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