Home | History | Annotate | Download | only in com.example.android.camera2basic
      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 {
     85         ORIENTATIONS.append(Surface.ROTATION_0, 90);
     86         ORIENTATIONS.append(Surface.ROTATION_90, 0);
     87         ORIENTATIONS.append(Surface.ROTATION_180, 270);
     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      */
    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      */
    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 
    417     public static Camera2BasicFragment newInstance() {
    418         return new Camera2BasicFragment();
    419     }
    420 
    421     @Override
    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
    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
    435     public void onActivityCreated(Bundle savedInstanceState) {
    436         super.onActivityCreated(savedInstanceState);
    437         mFile = new File(getActivity().getExternalFilesDir(null), "pic.jpg");
    438     }
    439 
    440     @Override
    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
    457     public void onPause() {
    458         closeCamera();
    459         stopBackgroundThread();
    460         super.onPause();
    461     }
    462 
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    768     private void takePicture() {
    769         lockFocus();
    770     }
    771 
    772     /**
    773      * Lock the focus as the first step for a still image capture.
    774      */
    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      */
    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      */
    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      */
    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      */
    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
    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 
    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 
    927         public ImageSaver(Image image, File file) {
    928             mImage = image;
    929             mFile = file;
    930         }
    931 
    932         @Override
    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
    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 
    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
    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