1 /*
2  * Copyright 2017 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.camera2basic;
18 
19 import android.Manifest;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.pm.PackageManager;
26 import android.content.res.Configuration;
27 import android.graphics.ImageFormat;
28 import android.graphics.Matrix;
29 import android.graphics.Point;
30 import android.graphics.RectF;
31 import android.graphics.SurfaceTexture;
32 import android.hardware.camera2.CameraAccessException;
33 import android.hardware.camera2.CameraCaptureSession;
34 import android.hardware.camera2.CameraCharacteristics;
35 import android.hardware.camera2.CameraDevice;
36 import android.hardware.camera2.CameraManager;
37 import android.hardware.camera2.CameraMetadata;
38 import android.hardware.camera2.CaptureRequest;
39 import android.hardware.camera2.CaptureResult;
40 import android.hardware.camera2.TotalCaptureResult;
41 import android.hardware.camera2.params.StreamConfigurationMap;
42 import android.media.Image;
43 import android.media.ImageReader;
44 import android.os.Bundle;
45 import android.os.Handler;
46 import android.os.HandlerThread;
47 import android.support.annotation.NonNull;
48 import android.support.v4.app.ActivityCompat;
49 import android.support.v4.app.DialogFragment;
50 import android.support.v4.app.Fragment;
51 import android.support.v4.content.ContextCompat;
52 import android.util.Log;
53 import android.util.Size;
54 import android.util.SparseIntArray;
55 import android.view.LayoutInflater;
56 import android.view.Surface;
57 import android.view.TextureView;
58 import android.view.View;
59 import android.view.ViewGroup;
60 import android.widget.Toast;
61 
62 import java.io.File;
63 import java.io.FileOutputStream;
64 import java.io.IOException;
65 import java.nio.ByteBuffer;
66 import java.util.ArrayList;
67 import java.util.Arrays;
68 import java.util.Collections;
69 import java.util.Comparator;
70 import java.util.List;
71 import java.util.concurrent.Semaphore;
72 import java.util.concurrent.TimeUnit;
73 
74 public class Camera2BasicFragment extends Fragment
75         implements View.OnClickListener, ActivityCompat.OnRequestPermissionsResultCallback {
76 
77     /**
78      * Conversion from screen rotation to JPEG orientation.
79      */
80     private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
81     private static final int REQUEST_CAMERA_PERMISSION = 1;
82     private static final String FRAGMENT_DIALOG = "dialog";
83 
84     static {
ORIENTATIONS.append(Surface.ROTATION_0, 90)85         ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0)86         ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270)87         ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180)88         ORIENTATIONS.append(Surface.ROTATION_270, 180);
89     }
90 
91     /**
92      * Tag for the {@link Log}.
93      */
94     private static final String TAG = "Camera2BasicFragment";
95 
96     /**
97      * Camera state: Showing camera preview.
98      */
99     private static final int STATE_PREVIEW = 0;
100 
101     /**
102      * Camera state: Waiting for the focus to be locked.
103      */
104     private static final int STATE_WAITING_LOCK = 1;
105 
106     /**
107      * Camera state: Waiting for the exposure to be precapture state.
108      */
109     private static final int STATE_WAITING_PRECAPTURE = 2;
110 
111     /**
112      * Camera state: Waiting for the exposure state to be something other than precapture.
113      */
114     private static final int STATE_WAITING_NON_PRECAPTURE = 3;
115 
116     /**
117      * Camera state: Picture was taken.
118      */
119     private static final int STATE_PICTURE_TAKEN = 4;
120 
121     /**
122      * Max preview width that is guaranteed by Camera2 API
123      */
124     private static final int MAX_PREVIEW_WIDTH = 1920;
125 
126     /**
127      * Max preview height that is guaranteed by Camera2 API
128      */
129     private static final int MAX_PREVIEW_HEIGHT = 1080;
130 
131     /**
132      * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a
133      * {@link TextureView}.
134      */
135     private final TextureView.SurfaceTextureListener mSurfaceTextureListener
136             = new TextureView.SurfaceTextureListener() {
137 
138         @Override
139         public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
140             openCamera(width, height);
141         }
142 
143         @Override
144         public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
145             configureTransform(width, height);
146         }
147 
148         @Override
149         public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
150             return true;
151         }
152 
153         @Override
154         public void onSurfaceTextureUpdated(SurfaceTexture texture) {
155         }
156 
157     };
158 
159     /**
160      * ID of the current {@link CameraDevice}.
161      */
162     private String mCameraId;
163 
164     /**
165      * An {@link AutoFitTextureView} for camera preview.
166      */
167     private AutoFitTextureView mTextureView;
168 
169     /**
170      * A {@link CameraCaptureSession } for camera preview.
171      */
172     private CameraCaptureSession mCaptureSession;
173 
174     /**
175      * A reference to the opened {@link CameraDevice}.
176      */
177     private CameraDevice mCameraDevice;
178 
179     /**
180      * The {@link android.util.Size} of camera preview.
181      */
182     private Size mPreviewSize;
183 
184     /**
185      * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.
186      */
187     private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
188 
189         @Override
190         public void onOpened(@NonNull CameraDevice cameraDevice) {
191             // This method is called when the camera is opened.  We start camera preview here.
192             mCameraOpenCloseLock.release();
193             mCameraDevice = cameraDevice;
194             createCameraPreviewSession();
195         }
196 
197         @Override
198         public void onDisconnected(@NonNull CameraDevice cameraDevice) {
199             mCameraOpenCloseLock.release();
200             cameraDevice.close();
201             mCameraDevice = null;
202         }
203 
204         @Override
205         public void onError(@NonNull CameraDevice cameraDevice, int error) {
206             mCameraOpenCloseLock.release();
207             cameraDevice.close();
208             mCameraDevice = null;
209             Activity activity = getActivity();
210             if (null != activity) {
211                 activity.finish();
212             }
213         }
214 
215     };
216 
217     /**
218      * An additional thread for running tasks that shouldn't block the UI.
219      */
220     private HandlerThread mBackgroundThread;
221 
222     /**
223      * A {@link Handler} for running tasks in the background.
224      */
225     private Handler mBackgroundHandler;
226 
227     /**
228      * An {@link ImageReader} that handles still image capture.
229      */
230     private ImageReader mImageReader;
231 
232     /**
233      * This is the output file for our picture.
234      */
235     private File mFile;
236 
237     /**
238      * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
239      * still image is ready to be saved.
240      */
241     private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
242             = new ImageReader.OnImageAvailableListener() {
243 
244         @Override
245         public void onImageAvailable(ImageReader reader) {
246             mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
247         }
248 
249     };
250 
251     /**
252      * {@link CaptureRequest.Builder} for the camera preview
253      */
254     private CaptureRequest.Builder mPreviewRequestBuilder;
255 
256     /**
257      * {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder}
258      */
259     private CaptureRequest mPreviewRequest;
260 
261     /**
262      * The current state of camera state for taking pictures.
263      *
264      * @see #mCaptureCallback
265      */
266     private int mState = STATE_PREVIEW;
267 
268     /**
269      * A {@link Semaphore} to prevent the app from exiting before closing the camera.
270      */
271     private Semaphore mCameraOpenCloseLock = new Semaphore(1);
272 
273     /**
274      * Whether the current camera device supports Flash or not.
275      */
276     private boolean mFlashSupported;
277 
278     /**
279      * Orientation of the camera sensor
280      */
281     private int mSensorOrientation;
282 
283     /**
284      * A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture.
285      */
286     private CameraCaptureSession.CaptureCallback mCaptureCallback
287             = new CameraCaptureSession.CaptureCallback() {
288 
289         private void process(CaptureResult result) {
290             switch (mState) {
291                 case STATE_PREVIEW: {
292                     // We have nothing to do when the camera preview is working normally.
293                     break;
294                 }
295                 case STATE_WAITING_LOCK: {
296                     Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
297                     if (afState == null) {
298                         captureStillPicture();
299                     } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
300                             CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
301                         // CONTROL_AE_STATE can be null on some devices
302                         Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
303                         if (aeState == null ||
304                                 aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
305                             mState = STATE_PICTURE_TAKEN;
306                             captureStillPicture();
307                         } else {
308                             runPrecaptureSequence();
309                         }
310                     }
311                     break;
312                 }
313                 case STATE_WAITING_PRECAPTURE: {
314                     // CONTROL_AE_STATE can be null on some devices
315                     Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
316                     if (aeState == null ||
317                             aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
318                             aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
319                         mState = STATE_WAITING_NON_PRECAPTURE;
320                     }
321                     break;
322                 }
323                 case STATE_WAITING_NON_PRECAPTURE: {
324                     // CONTROL_AE_STATE can be null on some devices
325                     Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
326                     if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
327                         mState = STATE_PICTURE_TAKEN;
328                         captureStillPicture();
329                     }
330                     break;
331                 }
332             }
333         }
334 
335         @Override
336         public void onCaptureProgressed(@NonNull CameraCaptureSession session,
337                                         @NonNull CaptureRequest request,
338                                         @NonNull CaptureResult partialResult) {
339             process(partialResult);
340         }
341 
342         @Override
343         public void onCaptureCompleted(@NonNull CameraCaptureSession session,
344                                        @NonNull CaptureRequest request,
345                                        @NonNull TotalCaptureResult result) {
346             process(result);
347         }
348 
349     };
350 
351     /**
352      * Shows a {@link Toast} on the UI thread.
353      *
354      * @param text The message to show
355      */
showToast(final String text)356     private void showToast(final String text) {
357         final Activity activity = getActivity();
358         if (activity != null) {
359             activity.runOnUiThread(new Runnable() {
360                 @Override
361                 public void run() {
362                     Toast.makeText(activity, text, Toast.LENGTH_SHORT).show();
363                 }
364             });
365         }
366     }
367 
368     /**
369      * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that
370      * is at least as large as the respective texture view size, and that is at most as large as the
371      * respective max size, and whose aspect ratio matches with the specified value. If such size
372      * doesn't exist, choose the largest one that is at most as large as the respective max size,
373      * and whose aspect ratio matches with the specified value.
374      *
375      * @param choices           The list of sizes that the camera supports for the intended output
376      *                          class
377      * @param textureViewWidth  The width of the texture view relative to sensor coordinate
378      * @param textureViewHeight The height of the texture view relative to sensor coordinate
379      * @param maxWidth          The maximum width that can be chosen
380      * @param maxHeight         The maximum height that can be chosen
381      * @param aspectRatio       The aspect ratio
382      * @return The optimal {@code Size}, or an arbitrary one if none were big enough
383      */
chooseOptimalSize(Size[] choices, int textureViewWidth, int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio)384     private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
385             int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
386 
387         // Collect the supported resolutions that are at least as big as the preview Surface
388         List<Size> bigEnough = new ArrayList<>();
389         // Collect the supported resolutions that are smaller than the preview Surface
390         List<Size> notBigEnough = new ArrayList<>();
391         int w = aspectRatio.getWidth();
392         int h = aspectRatio.getHeight();
393         for (Size option : choices) {
394             if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
395                     option.getHeight() == option.getWidth() * h / w) {
396                 if (option.getWidth() >= textureViewWidth &&
397                     option.getHeight() >= textureViewHeight) {
398                     bigEnough.add(option);
399                 } else {
400                     notBigEnough.add(option);
401                 }
402             }
403         }
404 
405         // Pick the smallest of those big enough. If there is no one big enough, pick the
406         // largest of those not big enough.
407         if (bigEnough.size() > 0) {
408             return Collections.min(bigEnough, new CompareSizesByArea());
409         } else if (notBigEnough.size() > 0) {
410             return Collections.max(notBigEnough, new CompareSizesByArea());
411         } else {
412             Log.e(TAG, "Couldn't find any suitable preview size");
413             return choices[0];
414         }
415     }
416 
newInstance()417     public static Camera2BasicFragment newInstance() {
418         return new Camera2BasicFragment();
419     }
420 
421     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)422     public View onCreateView(LayoutInflater inflater, ViewGroup container,
423                              Bundle savedInstanceState) {
424         return inflater.inflate(R.layout.fragment_camera2_basic, container, false);
425     }
426 
427     @Override
onViewCreated(final View view, Bundle savedInstanceState)428     public void onViewCreated(final View view, Bundle savedInstanceState) {
429         view.findViewById(R.id.picture).setOnClickListener(this);
430         view.findViewById(R.id.info).setOnClickListener(this);
431         mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture);
432     }
433 
434     @Override
onActivityCreated(Bundle savedInstanceState)435     public void onActivityCreated(Bundle savedInstanceState) {
436         super.onActivityCreated(savedInstanceState);
437         mFile = new File(getActivity().getExternalFilesDir(null), "pic.jpg");
438     }
439 
440     @Override
onResume()441     public void onResume() {
442         super.onResume();
443         startBackgroundThread();
444 
445         // When the screen is turned off and turned back on, the SurfaceTexture is already
446         // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
447         // a camera and start preview from here (otherwise, we wait until the surface is ready in
448         // the SurfaceTextureListener).
449         if (mTextureView.isAvailable()) {
450             openCamera(mTextureView.getWidth(), mTextureView.getHeight());
451         } else {
452             mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
453         }
454     }
455 
456     @Override
onPause()457     public void onPause() {
458         closeCamera();
459         stopBackgroundThread();
460         super.onPause();
461     }
462 
requestCameraPermission()463     private void requestCameraPermission() {
464         if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
465             new ConfirmationDialog().show(getChildFragmentManager(), FRAGMENT_DIALOG);
466         } else {
467             requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
468         }
469     }
470 
471     @Override
onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)472     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
473                                            @NonNull int[] grantResults) {
474         if (requestCode == REQUEST_CAMERA_PERMISSION) {
475             if (grantResults.length != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
476                 ErrorDialog.newInstance(getString(R.string.request_permission))
477                         .show(getChildFragmentManager(), FRAGMENT_DIALOG);
478             }
479         } else {
480             super.onRequestPermissionsResult(requestCode, permissions, grantResults);
481         }
482     }
483 
484     /**
485      * Sets up member variables related to camera.
486      *
487      * @param width  The width of available size for camera preview
488      * @param height The height of available size for camera preview
489      */
490     @SuppressWarnings("SuspiciousNameCombination")
setUpCameraOutputs(int width, int height)491     private void setUpCameraOutputs(int width, int height) {
492         Activity activity = getActivity();
493         CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
494         try {
495             for (String cameraId : manager.getCameraIdList()) {
496                 CameraCharacteristics characteristics
497                         = manager.getCameraCharacteristics(cameraId);
498 
499                 // We don't use a front facing camera in this sample.
500                 Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
501                 if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
502                     continue;
503                 }
504 
505                 StreamConfigurationMap map = characteristics.get(
506                         CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
507                 if (map == null) {
508                     continue;
509                 }
510 
511                 // For still image captures, we use the largest available size.
512                 Size largest = Collections.max(
513                         Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
514                         new CompareSizesByArea());
515                 mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
516                         ImageFormat.JPEG, /*maxImages*/2);
517                 mImageReader.setOnImageAvailableListener(
518                         mOnImageAvailableListener, mBackgroundHandler);
519 
520                 // Find out if we need to swap dimension to get the preview size relative to sensor
521                 // coordinate.
522                 int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
523                 //noinspection ConstantConditions
524                 mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
525                 boolean swappedDimensions = false;
526                 switch (displayRotation) {
527                     case Surface.ROTATION_0:
528                     case Surface.ROTATION_180:
529                         if (mSensorOrientation == 90 || mSensorOrientation == 270) {
530                             swappedDimensions = true;
531                         }
532                         break;
533                     case Surface.ROTATION_90:
534                     case Surface.ROTATION_270:
535                         if (mSensorOrientation == 0 || mSensorOrientation == 180) {
536                             swappedDimensions = true;
537                         }
538                         break;
539                     default:
540                         Log.e(TAG, "Display rotation is invalid: " + displayRotation);
541                 }
542 
543                 Point displaySize = new Point();
544                 activity.getWindowManager().getDefaultDisplay().getSize(displaySize);
545                 int rotatedPreviewWidth = width;
546                 int rotatedPreviewHeight = height;
547                 int maxPreviewWidth = displaySize.x;
548                 int maxPreviewHeight = displaySize.y;
549 
550                 if (swappedDimensions) {
551                     rotatedPreviewWidth = height;
552                     rotatedPreviewHeight = width;
553                     maxPreviewWidth = displaySize.y;
554                     maxPreviewHeight = displaySize.x;
555                 }
556 
557                 if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
558                     maxPreviewWidth = MAX_PREVIEW_WIDTH;
559                 }
560 
561                 if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
562                     maxPreviewHeight = MAX_PREVIEW_HEIGHT;
563                 }
564 
565                 // Danger, W.R.! Attempting to use too large a preview size could  exceed the camera
566                 // bus' bandwidth limitation, resulting in gorgeous previews but the storage of
567                 // garbage capture data.
568                 mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
569                         rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
570                         maxPreviewHeight, largest);
571 
572                 // We fit the aspect ratio of TextureView to the size of preview we picked.
573                 int orientation = getResources().getConfiguration().orientation;
574                 if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
575                     mTextureView.setAspectRatio(
576                             mPreviewSize.getWidth(), mPreviewSize.getHeight());
577                 } else {
578                     mTextureView.setAspectRatio(
579                             mPreviewSize.getHeight(), mPreviewSize.getWidth());
580                 }
581 
582                 // Check if the flash is supported.
583                 Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
584                 mFlashSupported = available == null ? false : available;
585 
586                 mCameraId = cameraId;
587                 return;
588             }
589         } catch (CameraAccessException e) {
590             e.printStackTrace();
591         } catch (NullPointerException e) {
592             // Currently an NPE is thrown when the Camera2API is used but not supported on the
593             // device this code runs.
594             ErrorDialog.newInstance(getString(R.string.camera_error))
595                     .show(getChildFragmentManager(), FRAGMENT_DIALOG);
596         }
597     }
598 
599     /**
600      * Opens the camera specified by {@link Camera2BasicFragment#mCameraId}.
601      */
openCamera(int width, int height)602     private void openCamera(int width, int height) {
603         if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
604                 != PackageManager.PERMISSION_GRANTED) {
605             requestCameraPermission();
606             return;
607         }
608         setUpCameraOutputs(width, height);
609         configureTransform(width, height);
610         Activity activity = getActivity();
611         CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
612         try {
613             if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
614                 throw new RuntimeException("Time out waiting to lock camera opening.");
615             }
616             manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
617         } catch (CameraAccessException e) {
618             e.printStackTrace();
619         } catch (InterruptedException e) {
620             throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
621         }
622     }
623 
624     /**
625      * Closes the current {@link CameraDevice}.
626      */
closeCamera()627     private void closeCamera() {
628         try {
629             mCameraOpenCloseLock.acquire();
630             if (null != mCaptureSession) {
631                 mCaptureSession.close();
632                 mCaptureSession = null;
633             }
634             if (null != mCameraDevice) {
635                 mCameraDevice.close();
636                 mCameraDevice = null;
637             }
638             if (null != mImageReader) {
639                 mImageReader.close();
640                 mImageReader = null;
641             }
642         } catch (InterruptedException e) {
643             throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
644         } finally {
645             mCameraOpenCloseLock.release();
646         }
647     }
648 
649     /**
650      * Starts a background thread and its {@link Handler}.
651      */
startBackgroundThread()652     private void startBackgroundThread() {
653         mBackgroundThread = new HandlerThread("CameraBackground");
654         mBackgroundThread.start();
655         mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
656     }
657 
658     /**
659      * Stops the background thread and its {@link Handler}.
660      */
stopBackgroundThread()661     private void stopBackgroundThread() {
662         mBackgroundThread.quitSafely();
663         try {
664             mBackgroundThread.join();
665             mBackgroundThread = null;
666             mBackgroundHandler = null;
667         } catch (InterruptedException e) {
668             e.printStackTrace();
669         }
670     }
671 
672     /**
673      * Creates a new {@link CameraCaptureSession} for camera preview.
674      */
createCameraPreviewSession()675     private void createCameraPreviewSession() {
676         try {
677             SurfaceTexture texture = mTextureView.getSurfaceTexture();
678             assert texture != null;
679 
680             // We configure the size of default buffer to be the size of camera preview we want.
681             texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
682 
683             // This is the output Surface we need to start preview.
684             Surface surface = new Surface(texture);
685 
686             // We set up a CaptureRequest.Builder with the output Surface.
687             mPreviewRequestBuilder
688                     = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
689             mPreviewRequestBuilder.addTarget(surface);
690 
691             // Here, we create a CameraCaptureSession for camera preview.
692             mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
693                     new CameraCaptureSession.StateCallback() {
694 
695                         @Override
696                         public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
697                             // The camera is already closed
698                             if (null == mCameraDevice) {
699                                 return;
700                             }
701 
702                             // When the session is ready, we start displaying the preview.
703                             mCaptureSession = cameraCaptureSession;
704                             try {
705                                 // Auto focus should be continuous for camera preview.
706                                 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
707                                         CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
708                                 // Flash is automatically enabled when necessary.
709                                 setAutoFlash(mPreviewRequestBuilder);
710 
711                                 // Finally, we start displaying the camera preview.
712                                 mPreviewRequest = mPreviewRequestBuilder.build();
713                                 mCaptureSession.setRepeatingRequest(mPreviewRequest,
714                                         mCaptureCallback, mBackgroundHandler);
715                             } catch (CameraAccessException e) {
716                                 e.printStackTrace();
717                             }
718                         }
719 
720                         @Override
721                         public void onConfigureFailed(
722                                 @NonNull CameraCaptureSession cameraCaptureSession) {
723                             showToast("Failed");
724                         }
725                     }, null
726             );
727         } catch (CameraAccessException e) {
728             e.printStackTrace();
729         }
730     }
731 
732     /**
733      * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`.
734      * This method should be called after the camera preview size is determined in
735      * setUpCameraOutputs and also the size of `mTextureView` is fixed.
736      *
737      * @param viewWidth  The width of `mTextureView`
738      * @param viewHeight The height of `mTextureView`
739      */
configureTransform(int viewWidth, int viewHeight)740     private void configureTransform(int viewWidth, int viewHeight) {
741         Activity activity = getActivity();
742         if (null == mTextureView || null == mPreviewSize || null == activity) {
743             return;
744         }
745         int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
746         Matrix matrix = new Matrix();
747         RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
748         RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
749         float centerX = viewRect.centerX();
750         float centerY = viewRect.centerY();
751         if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
752             bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
753             matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
754             float scale = Math.max(
755                     (float) viewHeight / mPreviewSize.getHeight(),
756                     (float) viewWidth / mPreviewSize.getWidth());
757             matrix.postScale(scale, scale, centerX, centerY);
758             matrix.postRotate(90 * (rotation - 2), centerX, centerY);
759         } else if (Surface.ROTATION_180 == rotation) {
760             matrix.postRotate(180, centerX, centerY);
761         }
762         mTextureView.setTransform(matrix);
763     }
764 
765     /**
766      * Initiate a still image capture.
767      */
takePicture()768     private void takePicture() {
769         lockFocus();
770     }
771 
772     /**
773      * Lock the focus as the first step for a still image capture.
774      */
lockFocus()775     private void lockFocus() {
776         try {
777             // This is how to tell the camera to lock focus.
778             mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
779                     CameraMetadata.CONTROL_AF_TRIGGER_START);
780             // Tell #mCaptureCallback to wait for the lock.
781             mState = STATE_WAITING_LOCK;
782             mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
783                     mBackgroundHandler);
784         } catch (CameraAccessException e) {
785             e.printStackTrace();
786         }
787     }
788 
789     /**
790      * Run the precapture sequence for capturing a still image. This method should be called when
791      * we get a response in {@link #mCaptureCallback} from {@link #lockFocus()}.
792      */
runPrecaptureSequence()793     private void runPrecaptureSequence() {
794         try {
795             // This is how to tell the camera to trigger.
796             mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
797                     CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
798             // Tell #mCaptureCallback to wait for the precapture sequence to be set.
799             mState = STATE_WAITING_PRECAPTURE;
800             mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
801                     mBackgroundHandler);
802         } catch (CameraAccessException e) {
803             e.printStackTrace();
804         }
805     }
806 
807     /**
808      * Capture a still picture. This method should be called when we get a response in
809      * {@link #mCaptureCallback} from both {@link #lockFocus()}.
810      */
captureStillPicture()811     private void captureStillPicture() {
812         try {
813             final Activity activity = getActivity();
814             if (null == activity || null == mCameraDevice) {
815                 return;
816             }
817             // This is the CaptureRequest.Builder that we use to take a picture.
818             final CaptureRequest.Builder captureBuilder =
819                     mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
820             captureBuilder.addTarget(mImageReader.getSurface());
821 
822             // Use the same AE and AF modes as the preview.
823             captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
824                     CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
825             setAutoFlash(captureBuilder);
826 
827             // Orientation
828             int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
829             captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
830 
831             CameraCaptureSession.CaptureCallback CaptureCallback
832                     = new CameraCaptureSession.CaptureCallback() {
833 
834                 @Override
835                 public void onCaptureCompleted(@NonNull CameraCaptureSession session,
836                                                @NonNull CaptureRequest request,
837                                                @NonNull TotalCaptureResult result) {
838                     showToast("Saved: " + mFile);
839                     Log.d(TAG, mFile.toString());
840                     unlockFocus();
841                 }
842             };
843 
844             mCaptureSession.stopRepeating();
845             mCaptureSession.abortCaptures();
846             mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
847         } catch (CameraAccessException e) {
848             e.printStackTrace();
849         }
850     }
851 
852     /**
853      * Retrieves the JPEG orientation from the specified screen rotation.
854      *
855      * @param rotation The screen rotation.
856      * @return The JPEG orientation (one of 0, 90, 270, and 360)
857      */
getOrientation(int rotation)858     private int getOrientation(int rotation) {
859         // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
860         // We have to take that into account and rotate JPEG properly.
861         // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS.
862         // For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
863         return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
864     }
865 
866     /**
867      * Unlock the focus. This method should be called when still image capture sequence is
868      * finished.
869      */
unlockFocus()870     private void unlockFocus() {
871         try {
872             // Reset the auto-focus trigger
873             mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
874                     CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
875             setAutoFlash(mPreviewRequestBuilder);
876             mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
877                     mBackgroundHandler);
878             // After this, the camera will go back to the normal state of preview.
879             mState = STATE_PREVIEW;
880             mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback,
881                     mBackgroundHandler);
882         } catch (CameraAccessException e) {
883             e.printStackTrace();
884         }
885     }
886 
887     @Override
onClick(View view)888     public void onClick(View view) {
889         switch (view.getId()) {
890             case R.id.picture: {
891                 takePicture();
892                 break;
893             }
894             case R.id.info: {
895                 Activity activity = getActivity();
896                 if (null != activity) {
897                     new AlertDialog.Builder(activity)
898                             .setMessage(R.string.intro_message)
899                             .setPositiveButton(android.R.string.ok, null)
900                             .show();
901                 }
902                 break;
903             }
904         }
905     }
906 
setAutoFlash(CaptureRequest.Builder requestBuilder)907     private void setAutoFlash(CaptureRequest.Builder requestBuilder) {
908         if (mFlashSupported) {
909             requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
910                     CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
911         }
912     }
913 
914     /**
915      * Saves a JPEG {@link Image} into the specified {@link File}.
916      */
917     private static class ImageSaver implements Runnable {
918 
919         /**
920          * The JPEG image
921          */
922         private final Image mImage;
923         /**
924          * The file we save the image into.
925          */
926         private final File mFile;
927 
ImageSaver(Image image, File file)928         ImageSaver(Image image, File file) {
929             mImage = image;
930             mFile = file;
931         }
932 
933         @Override
run()934         public void run() {
935             ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
936             byte[] bytes = new byte[buffer.remaining()];
937             buffer.get(bytes);
938             FileOutputStream output = null;
939             try {
940                 output = new FileOutputStream(mFile);
941                 output.write(bytes);
942             } catch (IOException e) {
943                 e.printStackTrace();
944             } finally {
945                 mImage.close();
946                 if (null != output) {
947                     try {
948                         output.close();
949                     } catch (IOException e) {
950                         e.printStackTrace();
951                     }
952                 }
953             }
954         }
955 
956     }
957 
958     /**
959      * Compares two {@code Size}s based on their areas.
960      */
961     static class CompareSizesByArea implements Comparator<Size> {
962 
963         @Override
compare(Size lhs, Size rhs)964         public int compare(Size lhs, Size rhs) {
965             // We cast here to ensure the multiplications won't overflow
966             return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
967                     (long) rhs.getWidth() * rhs.getHeight());
968         }
969 
970     }
971 
972     /**
973      * Shows an error message dialog.
974      */
975     public static class ErrorDialog extends DialogFragment {
976 
977         private static final String ARG_MESSAGE = "message";
978 
newInstance(String message)979         public static ErrorDialog newInstance(String message) {
980             ErrorDialog dialog = new ErrorDialog();
981             Bundle args = new Bundle();
982             args.putString(ARG_MESSAGE, message);
983             dialog.setArguments(args);
984             return dialog;
985         }
986 
987         @NonNull
988         @Override
onCreateDialog(Bundle savedInstanceState)989         public Dialog onCreateDialog(Bundle savedInstanceState) {
990             final Activity activity = getActivity();
991             return new AlertDialog.Builder(activity)
992                     .setMessage(getArguments().getString(ARG_MESSAGE))
993                     .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
994                         @Override
995                         public void onClick(DialogInterface dialogInterface, int i) {
996                             activity.finish();
997                         }
998                     })
999                     .create();
1000         }
1001 
1002     }
1003 
1004     /**
1005      * Shows OK/Cancel confirmation dialog about camera permission.
1006      */
1007     public static class ConfirmationDialog extends DialogFragment {
1008 
1009         @NonNull
1010         @Override
1011         public Dialog onCreateDialog(Bundle savedInstanceState) {
1012             final Fragment parent = getParentFragment();
1013             return new AlertDialog.Builder(getActivity())
1014                     .setMessage(R.string.request_permission)
1015                     .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1016                         @Override
1017                         public void onClick(DialogInterface dialog, int which) {
1018                             parent.requestPermissions(new String[]{Manifest.permission.CAMERA},
1019                                     REQUEST_CAMERA_PERMISSION);
1020                         }
1021                     })
1022                     .setNegativeButton(android.R.string.cancel,
1023                             new DialogInterface.OnClickListener() {
1024                                 @Override
1025                                 public void onClick(DialogInterface dialog, int which) {
1026                                     Activity activity = parent.getActivity();
1027                                     if (activity != null) {
1028                                         activity.finish();
1029                                     }
1030                                 }
1031                             })
1032                     .create();
1033         }
1034     }
1035 
1036 }
1037