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 (jpegBuilder != null) {
551                     jpegBuilder.setResult(result);
552                     sb.append("Saving JPEG as: ");
553                     sb.append(jpegBuilder.getSaveLocation());
554                 }
555                 if (rawBuilder != null) {
556                     rawBuilder.setResult(result);
557                     if (jpegBuilder != null) sb.append(", ");
558                     sb.append("Saving RAW as: ");
559                     sb.append(rawBuilder.getSaveLocation());
560                 }
561 
562                 // If we have all the results necessary, save the image to a file in the background.
563                 handleCompletionLocked(requestId, jpegBuilder, mJpegResultQueue);
564                 handleCompletionLocked(requestId, rawBuilder, mRawResultQueue);
565 
566                 finishedCaptureLocked();
567             }
568 
569             showToast(sb.toString());
570         }
571 
572         @Override
573         public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request,
574                                     CaptureFailure failure) {
575             int requestId = (int) request.getTag();
576             synchronized (mCameraStateLock) {
577                 mJpegResultQueue.remove(requestId);
578                 mRawResultQueue.remove(requestId);
579                 finishedCaptureLocked();
580             }
581             showToast("Capture failed!");
582         }
583 
584     };
585 
586     /**
587      * A {@link Handler} for showing {@link Toast}s on the UI thread.
588      */
589     private final Handler mMessageHandler = new Handler(Looper.getMainLooper()) {
590         @Override
591         public void handleMessage(Message msg) {
592             Activity activity = getActivity();
593             if (activity != null) {
594                 Toast.makeText(activity, (String) msg.obj, Toast.LENGTH_SHORT).show();
595             }
596         }
597     };
598 
newInstance()599     public static Camera2RawFragment newInstance() {
600         return new Camera2RawFragment();
601     }
602 
603     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)604     public View onCreateView(LayoutInflater inflater, ViewGroup container,
605                              Bundle savedInstanceState) {
606         return inflater.inflate(R.layout.fragment_camera2_basic, container, false);
607     }
608 
609     @Override
onViewCreated(final View view, Bundle savedInstanceState)610     public void onViewCreated(final View view, Bundle savedInstanceState) {
611         view.findViewById(R.id.picture).setOnClickListener(this);
612         view.findViewById(R.id.info).setOnClickListener(this);
613         mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture);
614 
615         // Setup a new OrientationEventListener.  This is used to handle rotation events like a
616         // 180 degree rotation that do not normally trigger a call to onCreate to do view re-layout
617         // or otherwise cause the preview TextureView's size to change.
618         mOrientationListener = new OrientationEventListener(getActivity(),
619                 SensorManager.SENSOR_DELAY_NORMAL) {
620             @Override
621             public void onOrientationChanged(int orientation) {
622                 if (mTextureView != null && mTextureView.isAvailable()) {
623                     configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
624                 }
625             }
626         };
627     }
628 
629     @Override
onResume()630     public void onResume() {
631         super.onResume();
632         startBackgroundThread();
633         openCamera();
634 
635         // When the screen is turned off and turned back on, the SurfaceTexture is already
636         // available, and "onSurfaceTextureAvailable" will not be called. In that case, we should
637         // configure the preview bounds here (otherwise, we wait until the surface is ready in
638         // the SurfaceTextureListener).
639         if (mTextureView.isAvailable()) {
640             configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
641         } else {
642             mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
643         }
644         if (mOrientationListener != null && mOrientationListener.canDetectOrientation()) {
645             mOrientationListener.enable();
646         }
647     }
648 
649     @Override
onPause()650     public void onPause() {
651         if (mOrientationListener != null) {
652             mOrientationListener.disable();
653         }
654         closeCamera();
655         stopBackgroundThread();
656         super.onPause();
657     }
658 
659     @Override
onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)660     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
661         if (requestCode == REQUEST_CAMERA_PERMISSIONS) {
662             for (int result : grantResults) {
663                 if (result != PackageManager.PERMISSION_GRANTED) {
664                     showMissingPermissionError();
665                     return;
666                 }
667             }
668         } else {
669             super.onRequestPermissionsResult(requestCode, permissions, grantResults);
670         }
671     }
672 
673     @Override
onClick(View view)674     public void onClick(View view) {
675         switch (view.getId()) {
676             case R.id.picture: {
677                 takePicture();
678                 break;
679             }
680             case R.id.info: {
681                 Activity activity = getActivity();
682                 if (null != activity) {
683                     new AlertDialog.Builder(activity)
684                             .setMessage(R.string.intro_message)
685                             .setPositiveButton(android.R.string.ok, null)
686                             .show();
687                 }
688                 break;
689             }
690         }
691     }
692 
693     /**
694      * Sets up state related to camera that is needed before opening a {@link CameraDevice}.
695      */
setUpCameraOutputs()696     private boolean setUpCameraOutputs() {
697         Activity activity = getActivity();
698         CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
699         if (manager == null) {
700             ErrorDialog.buildErrorDialog("This device doesn't support Camera2 API.").
701                     show(getFragmentManager(), "dialog");
702             return false;
703         }
704         try {
705             // Find a CameraDevice that supports RAW captures, and configure state.
706             for (String cameraId : manager.getCameraIdList()) {
707                 CameraCharacteristics characteristics
708                         = manager.getCameraCharacteristics(cameraId);
709 
710                 // We only use a camera that supports RAW in this sample.
711                 if (!contains(characteristics.get(
712                                 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES),
713                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
714                     continue;
715                 }
716 
717                 StreamConfigurationMap map = characteristics.get(
718                         CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
719 
720                 // For still image captures, we use the largest available size.
721                 Size largestJpeg = Collections.max(
722                         Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
723                         new CompareSizesByArea());
724 
725                 Size largestRaw = Collections.max(
726                         Arrays.asList(map.getOutputSizes(ImageFormat.RAW_SENSOR)),
727                         new CompareSizesByArea());
728 
729                 synchronized (mCameraStateLock) {
730                     // Set up ImageReaders for JPEG and RAW outputs.  Place these in a reference
731                     // counted wrapper to ensure they are only closed when all background tasks
732                     // using them are finished.
733                     if (mJpegImageReader == null || mJpegImageReader.getAndRetain() == null) {
734                         mJpegImageReader = new RefCountedAutoCloseable<>(
735                                 ImageReader.newInstance(largestJpeg.getWidth(),
736                                         largestJpeg.getHeight(), ImageFormat.JPEG, /*maxImages*/5));
737                     }
738                     mJpegImageReader.get().setOnImageAvailableListener(
739                             mOnJpegImageAvailableListener, mBackgroundHandler);
740 
741                     if (mRawImageReader == null || mRawImageReader.getAndRetain() == null) {
742                         mRawImageReader = new RefCountedAutoCloseable<>(
743                                 ImageReader.newInstance(largestRaw.getWidth(),
744                                         largestRaw.getHeight(), ImageFormat.RAW_SENSOR, /*maxImages*/ 5));
745                     }
746                     mRawImageReader.get().setOnImageAvailableListener(
747                             mOnRawImageAvailableListener, mBackgroundHandler);
748 
749                     mCharacteristics = characteristics;
750                     mCameraId = cameraId;
751                 }
752                 return true;
753             }
754         } catch (CameraAccessException e) {
755             e.printStackTrace();
756         }
757 
758         // If we found no suitable cameras for capturing RAW, warn the user.
759         ErrorDialog.buildErrorDialog("This device doesn't support capturing RAW photos").
760                 show(getFragmentManager(), "dialog");
761         return false;
762     }
763 
764     /**
765      * Opens the camera specified by {@link #mCameraId}.
766      */
767     @SuppressWarnings("MissingPermission")
openCamera()768     private void openCamera() {
769         if (!setUpCameraOutputs()) {
770             return;
771         }
772         if (!hasAllPermissionsGranted()) {
773             requestCameraPermissions();
774             return;
775         }
776 
777         Activity activity = getActivity();
778         CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
779         try {
780             // Wait for any previously running session to finish.
781             if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
782                 throw new RuntimeException("Time out waiting to lock camera opening.");
783             }
784 
785             String cameraId;
786             Handler backgroundHandler;
787             synchronized (mCameraStateLock) {
788                 cameraId = mCameraId;
789                 backgroundHandler = mBackgroundHandler;
790             }
791 
792             // Attempt to open the camera. mStateCallback will be called on the background handler's
793             // thread when this succeeds or fails.
794             manager.openCamera(cameraId, mStateCallback, backgroundHandler);
795         } catch (CameraAccessException e) {
796             e.printStackTrace();
797         } catch (InterruptedException e) {
798             throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
799         }
800     }
801 
802     /**
803      * Requests permissions necessary to use camera and save pictures.
804      */
requestCameraPermissions()805     private void requestCameraPermissions() {
806         if (shouldShowRationale()) {
807             PermissionConfirmationDialog.newInstance().show(getChildFragmentManager(), "dialog");
808         } else {
809             FragmentCompat.requestPermissions(this, CAMERA_PERMISSIONS, REQUEST_CAMERA_PERMISSIONS);
810         }
811     }
812 
813     /**
814      * Tells whether all the necessary permissions are granted to this app.
815      *
816      * @return True if all the required permissions are granted.
817      */
hasAllPermissionsGranted()818     private boolean hasAllPermissionsGranted() {
819         for (String permission : CAMERA_PERMISSIONS) {
820             if (ActivityCompat.checkSelfPermission(getActivity(), permission)
821                     != PackageManager.PERMISSION_GRANTED) {
822                 return false;
823             }
824         }
825         return true;
826     }
827 
828     /**
829      * Gets whether you should show UI with rationale for requesting the permissions.
830      *
831      * @return True if the UI should be shown.
832      */
shouldShowRationale()833     private boolean shouldShowRationale() {
834         for (String permission : CAMERA_PERMISSIONS) {
835             if (FragmentCompat.shouldShowRequestPermissionRationale(this, permission)) {
836                 return true;
837             }
838         }
839         return false;
840     }
841 
842     /**
843      * Shows that this app really needs the permission and finishes the app.
844      */
showMissingPermissionError()845     private void showMissingPermissionError() {
846         Activity activity = getActivity();
847         if (activity != null) {
848             Toast.makeText(activity, R.string.request_permission, Toast.LENGTH_SHORT).show();
849             activity.finish();
850         }
851     }
852 
853     /**
854      * Closes the current {@link CameraDevice}.
855      */
closeCamera()856     private void closeCamera() {
857         try {
858             mCameraOpenCloseLock.acquire();
859             synchronized (mCameraStateLock) {
860 
861                 // Reset state and clean up resources used by the camera.
862                 // Note: After calling this, the ImageReaders will be closed after any background
863                 // tasks saving Images from these readers have been completed.
864                 mPendingUserCaptures = 0;
865                 mState = STATE_CLOSED;
866                 if (null != mCaptureSession) {
867                     mCaptureSession.close();
868                     mCaptureSession = null;
869                 }
870                 if (null != mCameraDevice) {
871                     mCameraDevice.close();
872                     mCameraDevice = null;
873                 }
874                 if (null != mJpegImageReader) {
875                     mJpegImageReader.close();
876                     mJpegImageReader = null;
877                 }
878                 if (null != mRawImageReader) {
879                     mRawImageReader.close();
880                     mRawImageReader = null;
881                 }
882             }
883         } catch (InterruptedException e) {
884             throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
885         } finally {
886             mCameraOpenCloseLock.release();
887         }
888     }
889 
890     /**
891      * Starts a background thread and its {@link Handler}.
892      */
startBackgroundThread()893     private void startBackgroundThread() {
894         mBackgroundThread = new HandlerThread("CameraBackground");
895         mBackgroundThread.start();
896         synchronized (mCameraStateLock) {
897             mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
898         }
899     }
900 
901     /**
902      * Stops the background thread and its {@link Handler}.
903      */
stopBackgroundThread()904     private void stopBackgroundThread() {
905         mBackgroundThread.quitSafely();
906         try {
907             mBackgroundThread.join();
908             mBackgroundThread = null;
909             synchronized (mCameraStateLock) {
910                 mBackgroundHandler = null;
911             }
912         } catch (InterruptedException e) {
913             e.printStackTrace();
914         }
915     }
916 
917     /**
918      * Creates a new {@link CameraCaptureSession} for camera preview.
919      * <p/>
920      * Call this only with {@link #mCameraStateLock} held.
921      */
createCameraPreviewSessionLocked()922     private void createCameraPreviewSessionLocked() {
923         try {
924             SurfaceTexture texture = mTextureView.getSurfaceTexture();
925             // We configure the size of default buffer to be the size of camera preview we want.
926             texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
927 
928             // This is the output Surface we need to start preview.
929             Surface surface = new Surface(texture);
930 
931             // We set up a CaptureRequest.Builder with the output Surface.
932             mPreviewRequestBuilder
933                     = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
934             mPreviewRequestBuilder.addTarget(surface);
935 
936             // Here, we create a CameraCaptureSession for camera preview.
937             mCameraDevice.createCaptureSession(Arrays.asList(surface,
938                             mJpegImageReader.get().getSurface(),
939                             mRawImageReader.get().getSurface()), new CameraCaptureSession.StateCallback() {
940                         @Override
941                         public void onConfigured(CameraCaptureSession cameraCaptureSession) {
942                             synchronized (mCameraStateLock) {
943                                 // The camera is already closed
944                                 if (null == mCameraDevice) {
945                                     return;
946                                 }
947 
948                                 try {
949                                     setup3AControlsLocked(mPreviewRequestBuilder);
950                                     // Finally, we start displaying the camera preview.
951                                     cameraCaptureSession.setRepeatingRequest(
952                                             mPreviewRequestBuilder.build(),
953                                             mPreCaptureCallback, mBackgroundHandler);
954                                     mState = STATE_PREVIEW;
955                                 } catch (CameraAccessException | IllegalStateException e) {
956                                     e.printStackTrace();
957                                     return;
958                                 }
959                                 // When the session is ready, we start displaying the preview.
960                                 mCaptureSession = cameraCaptureSession;
961                             }
962                         }
963 
964                         @Override
965                         public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
966                             showToast("Failed to configure camera.");
967                         }
968                     }, mBackgroundHandler
969             );
970         } catch (CameraAccessException e) {
971             e.printStackTrace();
972         }
973     }
974 
975     /**
976      * Configure the given {@link CaptureRequest.Builder} to use auto-focus, auto-exposure, and
977      * auto-white-balance controls if available.
978      * <p/>
979      * Call this only with {@link #mCameraStateLock} held.
980      *
981      * @param builder the builder to configure.
982      */
setup3AControlsLocked(CaptureRequest.Builder builder)983     private void setup3AControlsLocked(CaptureRequest.Builder builder) {
984         // Enable auto-magical 3A run by camera device
985         builder.set(CaptureRequest.CONTROL_MODE,
986                 CaptureRequest.CONTROL_MODE_AUTO);
987 
988         Float minFocusDist =
989                 mCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE);
990 
991         // If MINIMUM_FOCUS_DISTANCE is 0, lens is fixed-focus and we need to skip the AF run.
992         mNoAFRun = (minFocusDist == null || minFocusDist == 0);
993 
994         if (!mNoAFRun) {
995             // If there is a "continuous picture" mode available, use it, otherwise default to AUTO.
996             if (contains(mCharacteristics.get(
997                             CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES),
998                     CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)) {
999                 builder.set(CaptureRequest.CONTROL_AF_MODE,
1000                         CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
1001             } else {
1002                 builder.set(CaptureRequest.CONTROL_AF_MODE,
1003                         CaptureRequest.CONTROL_AF_MODE_AUTO);
1004             }
1005         }
1006 
1007         // If there is an auto-magical flash control mode available, use it, otherwise default to
1008         // the "on" mode, which is guaranteed to always be available.
1009         if (contains(mCharacteristics.get(
1010                         CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES),
1011                 CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)) {
1012             builder.set(CaptureRequest.CONTROL_AE_MODE,
1013                     CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
1014         } else {
1015             builder.set(CaptureRequest.CONTROL_AE_MODE,
1016                     CaptureRequest.CONTROL_AE_MODE_ON);
1017         }
1018 
1019         // If there is an auto-magical white balance control mode available, use it.
1020         if (contains(mCharacteristics.get(
1021                         CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES),
1022                 CaptureRequest.CONTROL_AWB_MODE_AUTO)) {
1023             // Allow AWB to run auto-magically if this device supports this
1024             builder.set(CaptureRequest.CONTROL_AWB_MODE,
1025                     CaptureRequest.CONTROL_AWB_MODE_AUTO);
1026         }
1027     }
1028 
1029     /**
1030      * Configure the necessary {@link android.graphics.Matrix} transformation to `mTextureView`,
1031      * and start/restart the preview capture session if necessary.
1032      * <p/>
1033      * This method should be called after the camera state has been initialized in
1034      * setUpCameraOutputs.
1035      *
1036      * @param viewWidth  The width of `mTextureView`
1037      * @param viewHeight The height of `mTextureView`
1038      */
configureTransform(int viewWidth, int viewHeight)1039     private void configureTransform(int viewWidth, int viewHeight) {
1040         Activity activity = getActivity();
1041         synchronized (mCameraStateLock) {
1042             if (null == mTextureView || null == activity) {
1043                 return;
1044             }
1045 
1046             StreamConfigurationMap map = mCharacteristics.get(
1047                     CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
1048 
1049             // For still image captures, we always use the largest available size.
1050             Size largestJpeg = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
1051                     new CompareSizesByArea());
1052 
1053             // Find the rotation of the device relative to the native device orientation.
1054             int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
1055             Point displaySize = new Point();
1056             activity.getWindowManager().getDefaultDisplay().getSize(displaySize);
1057 
1058             // Find the rotation of the device relative to the camera sensor's orientation.
1059             int totalRotation = sensorToDeviceRotation(mCharacteristics, deviceRotation);
1060 
1061             // Swap the view dimensions for calculation as needed if they are rotated relative to
1062             // the sensor.
1063             boolean swappedDimensions = totalRotation == 90 || totalRotation == 270;
1064             int rotatedViewWidth = viewWidth;
1065             int rotatedViewHeight = viewHeight;
1066             int maxPreviewWidth = displaySize.x;
1067             int maxPreviewHeight = displaySize.y;
1068 
1069             if (swappedDimensions) {
1070                 rotatedViewWidth = viewHeight;
1071                 rotatedViewHeight = viewWidth;
1072                 maxPreviewWidth = displaySize.y;
1073                 maxPreviewHeight = displaySize.x;
1074             }
1075 
1076             // Preview should not be larger than display size and 1080p.
1077             if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
1078                 maxPreviewWidth = MAX_PREVIEW_WIDTH;
1079             }
1080 
1081             if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
1082                 maxPreviewHeight = MAX_PREVIEW_HEIGHT;
1083             }
1084 
1085             // Find the best preview size for these view dimensions and configured JPEG size.
1086             Size previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
1087                     rotatedViewWidth, rotatedViewHeight, maxPreviewWidth, maxPreviewHeight,
1088                     largestJpeg);
1089 
1090             if (swappedDimensions) {
1091                 mTextureView.setAspectRatio(
1092                         previewSize.getHeight(), previewSize.getWidth());
1093             } else {
1094                 mTextureView.setAspectRatio(
1095                         previewSize.getWidth(), previewSize.getHeight());
1096             }
1097 
1098             // Find rotation of device in degrees (reverse device orientation for front-facing
1099             // cameras).
1100             int rotation = (mCharacteristics.get(CameraCharacteristics.LENS_FACING) ==
1101                     CameraCharacteristics.LENS_FACING_FRONT) ?
1102                     (360 + ORIENTATIONS.get(deviceRotation)) % 360 :
1103                     (360 - ORIENTATIONS.get(deviceRotation)) % 360;
1104 
1105             Matrix matrix = new Matrix();
1106             RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
1107             RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth());
1108             float centerX = viewRect.centerX();
1109             float centerY = viewRect.centerY();
1110 
1111             // Initially, output stream images from the Camera2 API will be rotated to the native
1112             // device orientation from the sensor's orientation, and the TextureView will default to
1113             // scaling these buffers to fill it's view bounds.  If the aspect ratios and relative
1114             // orientations are correct, this is fine.
1115             //
1116             // However, if the device orientation has been rotated relative to its native
1117             // orientation so that the TextureView's dimensions are swapped relative to the
1118             // native device orientation, we must do the following to ensure the output stream
1119             // images are not incorrectly scaled by the TextureView:
1120             //   - Undo the scale-to-fill from the output buffer's dimensions (i.e. its dimensions
1121             //     in the native device orientation) to the TextureView's dimension.
1122             //   - Apply a scale-to-fill from the output buffer's rotated dimensions
1123             //     (i.e. its dimensions in the current device orientation) to the TextureView's
1124             //     dimensions.
1125             //   - Apply the rotation from the native device orientation to the current device
1126             //     rotation.
1127             if (Surface.ROTATION_90 == deviceRotation || Surface.ROTATION_270 == deviceRotation) {
1128                 bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
1129                 matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
1130                 float scale = Math.max(
1131                         (float) viewHeight / previewSize.getHeight(),
1132                         (float) viewWidth / previewSize.getWidth());
1133                 matrix.postScale(scale, scale, centerX, centerY);
1134 
1135             }
1136             matrix.postRotate(rotation, centerX, centerY);
1137 
1138             mTextureView.setTransform(matrix);
1139 
1140             // Start or restart the active capture session if the preview was initialized or
1141             // if its aspect ratio changed significantly.
1142             if (mPreviewSize == null || !checkAspectsEqual(previewSize, mPreviewSize)) {
1143                 mPreviewSize = previewSize;
1144                 if (mState != STATE_CLOSED) {
1145                     createCameraPreviewSessionLocked();
1146                 }
1147             }
1148         }
1149     }
1150 
1151     /**
1152      * Initiate a still image capture.
1153      * <p/>
1154      * This function sends a capture request that initiates a pre-capture sequence in our state
1155      * machine that waits for auto-focus to finish, ending in a "locked" state where the lens is no
1156      * longer moving, waits for auto-exposure to choose a good exposure value, and waits for
1157      * auto-white-balance to converge.
1158      */
takePicture()1159     private void takePicture() {
1160         synchronized (mCameraStateLock) {
1161             mPendingUserCaptures++;
1162 
1163             // If we already triggered a pre-capture sequence, or are in a state where we cannot
1164             // do this, return immediately.
1165             if (mState != STATE_PREVIEW) {
1166                 return;
1167             }
1168 
1169             try {
1170                 // Trigger an auto-focus run if camera is capable. If the camera is already focused,
1171                 // this should do nothing.
1172                 if (!mNoAFRun) {
1173                     mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
1174                             CameraMetadata.CONTROL_AF_TRIGGER_START);
1175                 }
1176 
1177                 // If this is not a legacy device, we can also trigger an auto-exposure metering
1178                 // run.
1179                 if (!isLegacyLocked()) {
1180                     // Tell the camera to lock focus.
1181                     mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
1182                             CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START);
1183                 }
1184 
1185                 // Update state machine to wait for auto-focus, auto-exposure, and
1186                 // auto-white-balance (aka. "3A") to converge.
1187                 mState = STATE_WAITING_FOR_3A_CONVERGENCE;
1188 
1189                 // Start a timer for the pre-capture sequence.
1190                 startTimerLocked();
1191 
1192                 // Replace the existing repeating request with one with updated 3A triggers.
1193                 mCaptureSession.capture(mPreviewRequestBuilder.build(), mPreCaptureCallback,
1194                         mBackgroundHandler);
1195             } catch (CameraAccessException e) {
1196                 e.printStackTrace();
1197             }
1198         }
1199     }
1200 
1201     /**
1202      * Send a capture request to the camera device that initiates a capture targeting the JPEG and
1203      * RAW outputs.
1204      * <p/>
1205      * Call this only with {@link #mCameraStateLock} held.
1206      */
captureStillPictureLocked()1207     private void captureStillPictureLocked() {
1208         try {
1209             final Activity activity = getActivity();
1210             if (null == activity || null == mCameraDevice) {
1211                 return;
1212             }
1213             // This is the CaptureRequest.Builder that we use to take a picture.
1214             final CaptureRequest.Builder captureBuilder =
1215                     mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
1216 
1217             captureBuilder.addTarget(mJpegImageReader.get().getSurface());
1218             captureBuilder.addTarget(mRawImageReader.get().getSurface());
1219 
1220             // Use the same AE and AF modes as the preview.
1221             setup3AControlsLocked(captureBuilder);
1222 
1223             // Set orientation.
1224             int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
1225             captureBuilder.set(CaptureRequest.JPEG_ORIENTATION,
1226                     sensorToDeviceRotation(mCharacteristics, rotation));
1227 
1228             // Set request tag to easily track results in callbacks.
1229             captureBuilder.setTag(mRequestCounter.getAndIncrement());
1230 
1231             CaptureRequest request = captureBuilder.build();
1232 
1233             // Create an ImageSaverBuilder in which to collect results, and add it to the queue
1234             // of active requests.
1235             ImageSaver.ImageSaverBuilder jpegBuilder = new ImageSaver.ImageSaverBuilder(activity)
1236                     .setCharacteristics(mCharacteristics);
1237             ImageSaver.ImageSaverBuilder rawBuilder = new ImageSaver.ImageSaverBuilder(activity)
1238                     .setCharacteristics(mCharacteristics);
1239 
1240             mJpegResultQueue.put((int) request.getTag(), jpegBuilder);
1241             mRawResultQueue.put((int) request.getTag(), rawBuilder);
1242 
1243             mCaptureSession.capture(request, mCaptureCallback, mBackgroundHandler);
1244 
1245         } catch (CameraAccessException e) {
1246             e.printStackTrace();
1247         }
1248     }
1249 
1250     /**
1251      * Called after a RAW/JPEG capture has completed; resets the AF trigger state for the
1252      * pre-capture sequence.
1253      * <p/>
1254      * Call this only with {@link #mCameraStateLock} held.
1255      */
finishedCaptureLocked()1256     private void finishedCaptureLocked() {
1257         try {
1258             // Reset the auto-focus trigger in case AF didn't run quickly enough.
1259             if (!mNoAFRun) {
1260                 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
1261                         CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
1262 
1263                 mCaptureSession.capture(mPreviewRequestBuilder.build(), mPreCaptureCallback,
1264                         mBackgroundHandler);
1265 
1266                 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
1267                         CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
1268             }
1269         } catch (CameraAccessException e) {
1270             e.printStackTrace();
1271         }
1272     }
1273 
1274     /**
1275      * Retrieve the next {@link Image} from a reference counted {@link ImageReader}, retaining
1276      * that {@link ImageReader} until that {@link Image} is no longer in use, and set this
1277      * {@link Image} as the result for the next request in the queue of pending requests.  If
1278      * all necessary information is available, begin saving the image to a file in a background
1279      * thread.
1280      *
1281      * @param pendingQueue the currently active requests.
1282      * @param reader       a reference counted wrapper containing an {@link ImageReader} from which
1283      *                     to acquire an image.
1284      */
dequeueAndSaveImage(TreeMap<Integer, ImageSaver.ImageSaverBuilder> pendingQueue, RefCountedAutoCloseable<ImageReader> reader)1285     private void dequeueAndSaveImage(TreeMap<Integer, ImageSaver.ImageSaverBuilder> pendingQueue,
1286                                      RefCountedAutoCloseable<ImageReader> reader) {
1287         synchronized (mCameraStateLock) {
1288             Map.Entry<Integer, ImageSaver.ImageSaverBuilder> entry =
1289                     pendingQueue.firstEntry();
1290             ImageSaver.ImageSaverBuilder builder = entry.getValue();
1291 
1292             // Increment reference count to prevent ImageReader from being closed while we
1293             // are saving its Images in a background thread (otherwise their resources may
1294             // be freed while we are writing to a file).
1295             if (reader == null || reader.getAndRetain() == null) {
1296                 Log.e(TAG, "Paused the activity before we could save the image," +
1297                         " ImageReader already closed.");
1298                 pendingQueue.remove(entry.getKey());
1299                 return;
1300             }
1301 
1302             Image image;
1303             try {
1304                 image = reader.get().acquireNextImage();
1305             } catch (IllegalStateException e) {
1306                 Log.e(TAG, "Too many images queued for saving, dropping image for request: " +
1307                         entry.getKey());
1308                 pendingQueue.remove(entry.getKey());
1309                 return;
1310             }
1311 
1312             builder.setRefCountedReader(reader).setImage(image);
1313 
1314             handleCompletionLocked(entry.getKey(), builder, pendingQueue);
1315         }
1316     }
1317 
1318     /**
1319      * Runnable that saves an {@link Image} into the specified {@link File}, and updates
1320      * {@link android.provider.MediaStore} to include the resulting file.
1321      * <p/>
1322      * This can be constructed through an {@link ImageSaverBuilder} as the necessary image and
1323      * result information becomes available.
1324      */
1325     private static class ImageSaver implements Runnable {
1326 
1327         /**
1328          * The image to save.
1329          */
1330         private final Image mImage;
1331         /**
1332          * The file we save the image into.
1333          */
1334         private final File mFile;
1335 
1336         /**
1337          * The CaptureResult for this image capture.
1338          */
1339         private final CaptureResult mCaptureResult;
1340 
1341         /**
1342          * The CameraCharacteristics for this camera device.
1343          */
1344         private final CameraCharacteristics mCharacteristics;
1345 
1346         /**
1347          * The Context to use when updating MediaStore with the saved images.
1348          */
1349         private final Context mContext;
1350 
1351         /**
1352          * A reference counted wrapper for the ImageReader that owns the given image.
1353          */
1354         private final RefCountedAutoCloseable<ImageReader> mReader;
1355 
ImageSaver(Image image, File file, CaptureResult result, CameraCharacteristics characteristics, Context context, RefCountedAutoCloseable<ImageReader> reader)1356         private ImageSaver(Image image, File file, CaptureResult result,
1357                            CameraCharacteristics characteristics, Context context,
1358                            RefCountedAutoCloseable<ImageReader> reader) {
1359             mImage = image;
1360             mFile = file;
1361             mCaptureResult = result;
1362             mCharacteristics = characteristics;
1363             mContext = context;
1364             mReader = reader;
1365         }
1366 
1367         @Override
run()1368         public void run() {
1369             boolean success = false;
1370             int format = mImage.getFormat();
1371             switch (format) {
1372                 case ImageFormat.JPEG: {
1373                     ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
1374                     byte[] bytes = new byte[buffer.remaining()];
1375                     buffer.get(bytes);
1376                     FileOutputStream output = null;
1377                     try {
1378                         output = new FileOutputStream(mFile);
1379                         output.write(bytes);
1380                         success = true;
1381                     } catch (IOException e) {
1382                         e.printStackTrace();
1383                     } finally {
1384                         mImage.close();
1385                         closeOutput(output);
1386                     }
1387                     break;
1388                 }
1389                 case ImageFormat.RAW_SENSOR: {
1390                     DngCreator dngCreator = new DngCreator(mCharacteristics, mCaptureResult);
1391                     FileOutputStream output = null;
1392                     try {
1393                         output = new FileOutputStream(mFile);
1394                         dngCreator.writeImage(output, mImage);
1395                         success = true;
1396                     } catch (IOException e) {
1397                         e.printStackTrace();
1398                     } finally {
1399                         mImage.close();
1400                         closeOutput(output);
1401                     }
1402                     break;
1403                 }
1404                 default: {
1405                     Log.e(TAG, "Cannot save image, unexpected image format:" + format);
1406                     break;
1407                 }
1408             }
1409 
1410             // Decrement reference count to allow ImageReader to be closed to free up resources.
1411             mReader.close();
1412 
1413             // If saving the file succeeded, update MediaStore.
1414             if (success) {
1415                 MediaScannerConnection.scanFile(mContext, new String[]{mFile.getPath()},
1416                 /*mimeTypes*/null, new MediaScannerConnection.MediaScannerConnectionClient() {
1417                     @Override
1418                     public void onMediaScannerConnected() {
1419                         // Do nothing
1420                     }
1421 
1422                     @Override
1423                     public void onScanCompleted(String path, Uri uri) {
1424                         Log.i(TAG, "Scanned " + path + ":");
1425                         Log.i(TAG, "-> uri=" + uri);
1426                     }
1427                 });
1428             }
1429         }
1430 
1431         /**
1432          * Builder class for constructing {@link ImageSaver}s.
1433          * <p/>
1434          * This class is thread safe.
1435          */
1436         public static class ImageSaverBuilder {
1437             private Image mImage;
1438             private File mFile;
1439             private CaptureResult mCaptureResult;
1440             private CameraCharacteristics mCharacteristics;
1441             private Context mContext;
1442             private RefCountedAutoCloseable<ImageReader> mReader;
1443 
1444             /**
1445              * Construct a new ImageSaverBuilder using the given {@link Context}.
1446              *
1447              * @param context a {@link Context} to for accessing the
1448              *                {@link android.provider.MediaStore}.
1449              */
ImageSaverBuilder(final Context context)1450             public ImageSaverBuilder(final Context context) {
1451                 mContext = context;
1452             }
1453 
setRefCountedReader( RefCountedAutoCloseable<ImageReader> reader)1454             public synchronized ImageSaverBuilder setRefCountedReader(
1455                     RefCountedAutoCloseable<ImageReader> reader) {
1456                 if (reader == null) throw new NullPointerException();
1457 
1458                 mReader = reader;
1459                 return this;
1460             }
1461 
setImage(final Image image)1462             public synchronized ImageSaverBuilder setImage(final Image image) {
1463                 if (image == null) throw new NullPointerException();
1464                 mImage = image;
1465                 return this;
1466             }
1467 
setFile(final File file)1468             public synchronized ImageSaverBuilder setFile(final File file) {
1469                 if (file == null) throw new NullPointerException();
1470                 mFile = file;
1471                 return this;
1472             }
1473 
setResult(final CaptureResult result)1474             public synchronized ImageSaverBuilder setResult(final CaptureResult result) {
1475                 if (result == null) throw new NullPointerException();
1476                 mCaptureResult = result;
1477                 return this;
1478             }
1479 
setCharacteristics( final CameraCharacteristics characteristics)1480             public synchronized ImageSaverBuilder setCharacteristics(
1481                     final CameraCharacteristics characteristics) {
1482                 if (characteristics == null) throw new NullPointerException();
1483                 mCharacteristics = characteristics;
1484                 return this;
1485             }
1486 
buildIfComplete()1487             public synchronized ImageSaver buildIfComplete() {
1488                 if (!isComplete()) {
1489                     return null;
1490                 }
1491                 return new ImageSaver(mImage, mFile, mCaptureResult, mCharacteristics, mContext,
1492                         mReader);
1493             }
1494 
getSaveLocation()1495             public synchronized String getSaveLocation() {
1496                 return (mFile == null) ? "Unknown" : mFile.toString();
1497             }
1498 
isComplete()1499             private boolean isComplete() {
1500                 return mImage != null && mFile != null && mCaptureResult != null
1501                         && mCharacteristics != null;
1502             }
1503         }
1504     }
1505 
1506     // Utility classes and methods:
1507     // *********************************************************************************************
1508 
1509     /**
1510      * Comparator based on area of the given {@link Size} objects.
1511      */
1512     static class CompareSizesByArea implements Comparator<Size> {
1513 
1514         @Override
compare(Size lhs, Size rhs)1515         public int compare(Size lhs, Size rhs) {
1516             // We cast here to ensure the multiplications won't overflow
1517             return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
1518                     (long) rhs.getWidth() * rhs.getHeight());
1519         }
1520 
1521     }
1522 
1523     /**
1524      * A dialog fragment for displaying non-recoverable errors; this {@ling Activity} will be
1525      * finished once the dialog has been acknowledged by the user.
1526      */
1527     public static class ErrorDialog extends DialogFragment {
1528 
1529         private String mErrorMessage;
1530 
ErrorDialog()1531         public ErrorDialog() {
1532             mErrorMessage = "Unknown error occurred!";
1533         }
1534 
1535         // Build a dialog with a custom message (Fragments require default constructor).
buildErrorDialog(String errorMessage)1536         public static ErrorDialog buildErrorDialog(String errorMessage) {
1537             ErrorDialog dialog = new ErrorDialog();
1538             dialog.mErrorMessage = errorMessage;
1539             return dialog;
1540         }
1541 
1542         @Override
onCreateDialog(Bundle savedInstanceState)1543         public Dialog onCreateDialog(Bundle savedInstanceState) {
1544             final Activity activity = getActivity();
1545             return new AlertDialog.Builder(activity)
1546                     .setMessage(mErrorMessage)
1547                     .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1548                         @Override
1549                         public void onClick(DialogInterface dialogInterface, int i) {
1550                             activity.finish();
1551                         }
1552                     })
1553                     .create();
1554         }
1555     }
1556 
1557     /**
1558      * A wrapper for an {@link AutoCloseable} object that implements reference counting to allow
1559      * for resource management.
1560      */
1561     public static class RefCountedAutoCloseable<T extends AutoCloseable> implements AutoCloseable {
1562         private T mObject;
1563         private long mRefCount = 0;
1564 
1565         /**
1566          * Wrap the given object.
1567          *
1568          * @param object an object to wrap.
1569          */
1570         public RefCountedAutoCloseable(T object) {
1571             if (object == null) throw new NullPointerException();
1572             mObject = object;
1573         }
1574 
1575         /**
1576          * Increment the reference count and return the wrapped object.
1577          *
1578          * @return the wrapped object, or null if the object has been released.
1579          */
1580         public synchronized T getAndRetain() {
1581             if (mRefCount < 0) {
1582                 return null;
1583             }
1584             mRefCount++;
1585             return mObject;
1586         }
1587 
1588         /**
1589          * Return the wrapped object.
1590          *
1591          * @return the wrapped object, or null if the object has been released.
1592          */
1593         public synchronized T get() {
1594             return mObject;
1595         }
1596 
1597         /**
1598          * Decrement the reference count and release the wrapped object if there are no other
1599          * users retaining this object.
1600          */
1601         @Override
1602         public synchronized void close() {
1603             if (mRefCount >= 0) {
1604                 mRefCount--;
1605                 if (mRefCount < 0) {
1606                     try {
1607                         mObject.close();
1608                     } catch (Exception e) {
1609                         throw new RuntimeException(e);
1610                     } finally {
1611                         mObject = null;
1612                     }
1613                 }
1614             }
1615         }
1616     }
1617 
1618     /**
1619      * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that
1620      * is at least as large as the respective texture view size, and that is at most as large as the
1621      * respective max size, and whose aspect ratio matches with the specified value. If such size
1622      * doesn't exist, choose the largest one that is at most as large as the respective max size,
1623      * and whose aspect ratio matches with the specified value.
1624      *
1625      * @param choices           The list of sizes that the camera supports for the intended output
1626      *                          class
1627      * @param textureViewWidth  The width of the texture view relative to sensor coordinate
1628      * @param textureViewHeight The height of the texture view relative to sensor coordinate
1629      * @param maxWidth          The maximum width that can be chosen
1630      * @param maxHeight         The maximum height that can be chosen
1631      * @param aspectRatio       The aspect ratio
1632      * @return The optimal {@code Size}, or an arbitrary one if none were big enough
1633      */
1634     private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
1635             int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
1636         // Collect the supported resolutions that are at least as big as the preview Surface
1637         List<Size> bigEnough = new ArrayList<>();
1638         // Collect the supported resolutions that are smaller than the preview Surface
1639         List<Size> notBigEnough = new ArrayList<>();
1640         int w = aspectRatio.getWidth();
1641         int h = aspectRatio.getHeight();
1642         for (Size option : choices) {
1643             if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
1644                     option.getHeight() == option.getWidth() * h / w) {
1645                 if (option.getWidth() >= textureViewWidth &&
1646                     option.getHeight() >= textureViewHeight) {
1647                     bigEnough.add(option);
1648                 } else {
1649                     notBigEnough.add(option);
1650                 }
1651             }
1652         }
1653 
1654         // Pick the smallest of those big enough. If there is no one big enough, pick the
1655         // largest of those not big enough.
1656         if (bigEnough.size() > 0) {
1657             return Collections.min(bigEnough, new CompareSizesByArea());
1658         } else if (notBigEnough.size() > 0) {
1659             return Collections.max(notBigEnough, new CompareSizesByArea());
1660         } else {
1661             Log.e(TAG, "Couldn't find any suitable preview size");
1662             return choices[0];
1663         }
1664     }
1665 
1666     /**
1667      * Generate a string containing a formatted timestamp with the current date and time.
1668      *
1669      * @return a {@link String} representing a time.
1670      */
1671     private static String generateTimestamp() {
1672         SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SSS", Locale.US);
1673         return sdf.format(new Date());
1674     }
1675 
1676     /**
1677      * Cleanup the given {@link OutputStream}.
1678      *
1679      * @param outputStream the stream to close.
1680      */
1681     private static void closeOutput(OutputStream outputStream) {
1682         if (null != outputStream) {
1683             try {
1684                 outputStream.close();
1685             } catch (IOException e) {
1686                 e.printStackTrace();
1687             }
1688         }
1689     }
1690 
1691     /**
1692      * Return true if the given array contains the given integer.
1693      *
1694      * @param modes array to check.
1695      * @param mode  integer to get for.
1696      * @return true if the array contains the given integer, otherwise false.
1697      */
1698     private static boolean contains(int[] modes, int mode) {
1699         if (modes == null) {
1700             return false;
1701         }
1702         for (int i : modes) {
1703             if (i == mode) {
1704                 return true;
1705             }
1706         }
1707         return false;
1708     }
1709 
1710     /**
1711      * Return true if the two given {@link Size}s have the same aspect ratio.
1712      *
1713      * @param a first {@link Size} to compare.
1714      * @param b second {@link Size} to compare.
1715      * @return true if the sizes have the same aspect ratio, otherwise false.
1716      */
1717     private static boolean checkAspectsEqual(Size a, Size b) {
1718         double aAspect = a.getWidth() / (double) a.getHeight();
1719         double bAspect = b.getWidth() / (double) b.getHeight();
1720         return Math.abs(aAspect - bAspect) <= ASPECT_RATIO_TOLERANCE;
1721     }
1722 
1723     /**
1724      * Rotation need to transform from the camera sensor orientation to the device's current
1725      * orientation.
1726      *
1727      * @param c                 the {@link CameraCharacteristics} to query for the camera sensor
1728      *                          orientation.
1729      * @param deviceOrientation the current device orientation relative to the native device
1730      *                          orientation.
1731      * @return the total rotation from the sensor orientation to the current device orientation.
1732      */
1733     private static int sensorToDeviceRotation(CameraCharacteristics c, int deviceOrientation) {
1734         int sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);
1735 
1736         // Get device orientation in degrees
1737         deviceOrientation = ORIENTATIONS.get(deviceOrientation);
1738 
1739         // Reverse device orientation for front-facing cameras
1740         if (c.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
1741             deviceOrientation = -deviceOrientation;
1742         }
1743 
1744         // Calculate desired JPEG orientation relative to camera orientation to make
1745         // the image upright relative to the device orientation
1746         return (sensorOrientation - deviceOrientation + 360) % 360;
1747     }
1748 
1749     /**
1750      * Shows a {@link Toast} on the UI thread.
1751      *
1752      * @param text The message to show.
1753      */
1754     private void showToast(String text) {
1755         // We show a Toast by sending request message to mMessageHandler. This makes sure that the
1756         // Toast is shown on the UI thread.
1757         Message message = Message.obtain();
1758         message.obj = text;
1759         mMessageHandler.sendMessage(message);
1760     }
1761 
1762     /**
1763      * If the given request has been completed, remove it from the queue of active requests and
1764      * send an {@link ImageSaver} with the results from this request to a background thread to
1765      * save a file.
1766      * <p/>
1767      * Call this only with {@link #mCameraStateLock} held.
1768      *
1769      * @param requestId the ID of the {@link CaptureRequest} to handle.
1770      * @param builder   the {@link ImageSaver.ImageSaverBuilder} for this request.
1771      * @param queue     the queue to remove this request from, if completed.
1772      */
1773     private void handleCompletionLocked(int requestId, ImageSaver.ImageSaverBuilder builder,
1774                                         TreeMap<Integer, ImageSaver.ImageSaverBuilder> queue) {
1775         if (builder == null) return;
1776         ImageSaver saver = builder.buildIfComplete();
1777         if (saver != null) {
1778             queue.remove(requestId);
1779             AsyncTask.THREAD_POOL_EXECUTOR.execute(saver);
1780         }
1781     }
1782 
1783     /**
1784      * Check if we are using a device that only supports the LEGACY hardware level.
1785      * <p/>
1786      * Call this only with {@link #mCameraStateLock} held.
1787      *
1788      * @return true if this is a legacy device.
1789      */
1790     private boolean isLegacyLocked() {
1791         return mCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
1792                 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
1793     }
1794 
1795     /**
1796      * Start the timer for the pre-capture sequence.
1797      * <p/>
1798      * Call this only with {@link #mCameraStateLock} held.
1799      */
1800     private void startTimerLocked() {
1801         mCaptureTimer = SystemClock.elapsedRealtime();
1802     }
1803 
1804     /**
1805      * Check if the timer for the pre-capture sequence has been hit.
1806      * <p/>
1807      * Call this only with {@link #mCameraStateLock} held.
1808      *
1809      * @return true if the timeout occurred.
1810      */
1811     private boolean hitTimeoutLocked() {
1812         return (SystemClock.elapsedRealtime() - mCaptureTimer) > PRECAPTURE_TIMEOUT_MS;
1813     }
1814 
1815     /**
1816      * A dialog that explains about the necessary permissions.
1817      */
1818     public static class PermissionConfirmationDialog extends DialogFragment {
1819 
1820         public static PermissionConfirmationDialog newInstance() {
1821             return new PermissionConfirmationDialog();
1822         }
1823 
1824         @Override
1825         public Dialog onCreateDialog(Bundle savedInstanceState) {
1826             final Fragment parent = getParentFragment();
1827             return new AlertDialog.Builder(getActivity())
1828                     .setMessage(R.string.request_permission)
1829                     .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1830                         @Override
1831                         public void onClick(DialogInterface dialog, int which) {
1832                             FragmentCompat.requestPermissions(parent, CAMERA_PERMISSIONS,
1833                                     REQUEST_CAMERA_PERMISSIONS);
1834                         }
1835                     })
1836                     .setNegativeButton(android.R.string.cancel,
1837                             new DialogInterface.OnClickListener() {
1838                                 @Override
1839                                 public void onClick(DialogInterface dialog, int which) {
1840                                     getActivity().finish();
1841                                 }
1842                             })
1843                     .create();
1844         }
1845 
1846     }
1847 
1848 }
1849