1 /*
2  * Copyright 2014 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.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.content.res.Configuration;
29 import android.graphics.ImageFormat;
30 import android.graphics.Matrix;
31 import android.graphics.Point;
32 import android.graphics.RectF;
33 import android.graphics.SurfaceTexture;
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.CaptureRequest;
41 import android.hardware.camera2.CaptureResult;
42 import android.hardware.camera2.TotalCaptureResult;
43 import android.hardware.camera2.params.StreamConfigurationMap;
44 import android.media.Image;
45 import android.media.ImageReader;
46 import android.os.Bundle;
47 import android.os.Handler;
48 import android.os.HandlerThread;
49 import android.support.annotation.NonNull;
50 import android.support.v13.app.FragmentCompat;
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, FragmentCompat.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 (FragmentCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
465             new ConfirmationDialog().show(getChildFragmentManager(), FRAGMENT_DIALOG);
466         } else {
467             FragmentCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
468                     REQUEST_CAMERA_PERMISSION);
469         }
470     }
471 
472     @Override
onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)473     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
474                                            @NonNull int[] grantResults) {
475         if (requestCode == REQUEST_CAMERA_PERMISSION) {
476             if (grantResults.length != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
477                 ErrorDialog.newInstance(getString(R.string.request_permission))
478                         .show(getChildFragmentManager(), FRAGMENT_DIALOG);
479             }
480         } else {
481             super.onRequestPermissionsResult(requestCode, permissions, grantResults);
482         }
483     }
484 
485     /**
486      * Sets up member variables related to camera.
487      *
488      * @param width  The width of available size for camera preview
489      * @param height The height of available size for camera preview
490      */
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.capture(captureBuilder.build(), CaptureCallback, null);
846         } catch (CameraAccessException e) {
847             e.printStackTrace();
848         }
849     }
850 
851     /**
852      * Retrieves the JPEG orientation from the specified screen rotation.
853      *
854      * @param rotation The screen rotation.
855      * @return The JPEG orientation (one of 0, 90, 270, and 360)
856      */
getOrientation(int rotation)857     private int getOrientation(int rotation) {
858         // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
859         // We have to take that into account and rotate JPEG properly.
860         // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS.
861         // For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
862         return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
863     }
864 
865     /**
866      * Unlock the focus. This method should be called when still image capture sequence is
867      * finished.
868      */
unlockFocus()869     private void unlockFocus() {
870         try {
871             // Reset the auto-focus trigger
872             mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
873                     CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
874             setAutoFlash(mPreviewRequestBuilder);
875             mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
876                     mBackgroundHandler);
877             // After this, the camera will go back to the normal state of preview.
878             mState = STATE_PREVIEW;
879             mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback,
880                     mBackgroundHandler);
881         } catch (CameraAccessException e) {
882             e.printStackTrace();
883         }
884     }
885 
886     @Override
onClick(View view)887     public void onClick(View view) {
888         switch (view.getId()) {
889             case R.id.picture: {
890                 takePicture();
891                 break;
892             }
893             case R.id.info: {
894                 Activity activity = getActivity();
895                 if (null != activity) {
896                     new AlertDialog.Builder(activity)
897                             .setMessage(R.string.intro_message)
898                             .setPositiveButton(android.R.string.ok, null)
899                             .show();
900                 }
901                 break;
902             }
903         }
904     }
905 
setAutoFlash(CaptureRequest.Builder requestBuilder)906     private void setAutoFlash(CaptureRequest.Builder requestBuilder) {
907         if (mFlashSupported) {
908             requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
909                     CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
910         }
911     }
912 
913     /**
914      * Saves a JPEG {@link Image} into the specified {@link File}.
915      */
916     private static class ImageSaver implements Runnable {
917 
918         /**
919          * The JPEG image
920          */
921         private final Image mImage;
922         /**
923          * The file we save the image into.
924          */
925         private final File mFile;
926 
ImageSaver(Image image, File file)927         public ImageSaver(Image image, File file) {
928             mImage = image;
929             mFile = file;
930         }
931 
932         @Override
run()933         public void run() {
934             ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
935             byte[] bytes = new byte[buffer.remaining()];
936             buffer.get(bytes);
937             FileOutputStream output = null;
938             try {
939                 output = new FileOutputStream(mFile);
940                 output.write(bytes);
941             } catch (IOException e) {
942                 e.printStackTrace();
943             } finally {
944                 mImage.close();
945                 if (null != output) {
946                     try {
947                         output.close();
948                     } catch (IOException e) {
949                         e.printStackTrace();
950                     }
951                 }
952             }
953         }
954 
955     }
956 
957     /**
958      * Compares two {@code Size}s based on their areas.
959      */
960     static class CompareSizesByArea implements Comparator<Size> {
961 
962         @Override
compare(Size lhs, Size rhs)963         public int compare(Size lhs, Size rhs) {
964             // We cast here to ensure the multiplications won't overflow
965             return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
966                     (long) rhs.getWidth() * rhs.getHeight());
967         }
968 
969     }
970 
971     /**
972      * Shows an error message dialog.
973      */
974     public static class ErrorDialog extends DialogFragment {
975 
976         private static final String ARG_MESSAGE = "message";
977 
newInstance(String message)978         public static ErrorDialog newInstance(String message) {
979             ErrorDialog dialog = new ErrorDialog();
980             Bundle args = new Bundle();
981             args.putString(ARG_MESSAGE, message);
982             dialog.setArguments(args);
983             return dialog;
984         }
985 
986         @Override
onCreateDialog(Bundle savedInstanceState)987         public Dialog onCreateDialog(Bundle savedInstanceState) {
988             final Activity activity = getActivity();
989             return new AlertDialog.Builder(activity)
990                     .setMessage(getArguments().getString(ARG_MESSAGE))
991                     .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
992                         @Override
993                         public void onClick(DialogInterface dialogInterface, int i) {
994                             activity.finish();
995                         }
996                     })
997                     .create();
998         }
999 
1000     }
1001 
1002     /**
1003      * Shows OK/Cancel confirmation dialog about camera permission.
1004      */
1005     public static class ConfirmationDialog extends DialogFragment {
1006 
1007         @Override
1008         public Dialog onCreateDialog(Bundle savedInstanceState) {
1009             final Fragment parent = getParentFragment();
1010             return new AlertDialog.Builder(getActivity())
1011                     .setMessage(R.string.request_permission)
1012                     .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1013                         @Override
1014                         public void onClick(DialogInterface dialog, int which) {
1015                             FragmentCompat.requestPermissions(parent,
1016                                     new String[]{Manifest.permission.CAMERA},
1017                                     REQUEST_CAMERA_PERMISSION);
1018                         }
1019                     })
1020                     .setNegativeButton(android.R.string.cancel,
1021                             new DialogInterface.OnClickListener() {
1022                                 @Override
1023                                 public void onClick(DialogInterface dialog, int which) {
1024                                     Activity activity = parent.getActivity();
1025                                     if (activity != null) {
1026                                         activity.finish();
1027                                     }
1028                                 }
1029                             })
1030                     .create();
1031         }
1032     }
1033 
1034 }
1035