Home | History | Annotate | Download | only in com.example.android.camera2video
      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.camera2video;
     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.Matrix;
     30 import android.graphics.RectF;
     31 import android.graphics.SurfaceTexture;
     32 import android.hardware.camera2.CameraAccessException;
     33 import android.hardware.camera2.CameraCaptureSession;
     34 import android.hardware.camera2.CameraCharacteristics;
     35 import android.hardware.camera2.CameraDevice;
     36 import android.hardware.camera2.CameraManager;
     37 import android.hardware.camera2.CameraMetadata;
     38 import android.hardware.camera2.CaptureRequest;
     39 import android.hardware.camera2.params.StreamConfigurationMap;
     40 import android.media.MediaRecorder;
     41 import android.os.Bundle;
     42 import android.os.Handler;
     43 import android.os.HandlerThread;
     44 import android.support.annotation.NonNull;
     45 import android.support.v13.app.FragmentCompat;
     46 import android.support.v4.app.ActivityCompat;
     47 import android.util.Log;
     48 import android.util.Size;
     49 import android.util.SparseIntArray;
     50 import android.view.LayoutInflater;
     51 import android.view.Surface;
     52 import android.view.TextureView;
     53 import android.view.View;
     54 import android.view.ViewGroup;
     55 import android.widget.Button;
     56 import android.widget.Toast;
     57 
     58 import java.io.IOException;
     59 import java.util.ArrayList;
     60 import java.util.Arrays;
     61 import java.util.Collections;
     62 import java.util.Comparator;
     63 import java.util.List;
     64 import java.util.concurrent.Semaphore;
     65 import java.util.concurrent.TimeUnit;
     66 
     67 public class Camera2VideoFragment extends Fragment
     68         implements View.OnClickListener, FragmentCompat.OnRequestPermissionsResultCallback {
     69 
     70     private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90;
     71     private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270;
     72     private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray();
     73     private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray();
     74 
     75     private static final String TAG = "Camera2VideoFragment";
     76     private static final int REQUEST_VIDEO_PERMISSIONS = 1;
     77     private static final String FRAGMENT_DIALOG = "dialog";
     78 
     79     private static final String[] VIDEO_PERMISSIONS = {
     80             Manifest.permission.CAMERA,
     81             Manifest.permission.RECORD_AUDIO,
     82     };
     83 
     84     static {
     85         DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90);
     86         DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0);
     87         DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270);
     88         DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180);
     89     }
     90 
     91     static {
     92         INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270);
     93         INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180);
     94         INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90);
     95         INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0);
     96     }
     97 
     98     /**
     99      * An {@link AutoFitTextureView} for camera preview.
    100      */
    101     private AutoFitTextureView mTextureView;
    102 
    103     /**
    104      * Button to record video
    105      */
    106     private Button mButtonVideo;
    107 
    108     /**
    109      * A refernce to the opened {@link android.hardware.camera2.CameraDevice}.
    110      */
    111     private CameraDevice mCameraDevice;
    112 
    113     /**
    114      * A reference to the current {@link android.hardware.camera2.CameraCaptureSession} for
    115      * preview.
    116      */
    117     private CameraCaptureSession mPreviewSession;
    118 
    119     /**
    120      * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a
    121      * {@link TextureView}.
    122      */
    123     private TextureView.SurfaceTextureListener mSurfaceTextureListener
    124             = new TextureView.SurfaceTextureListener() {
    125 
    126         @Override
    127         public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
    128                                               int width, int height) {
    129             openCamera(width, height);
    130         }
    131 
    132         @Override
    133         public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
    134                                                 int width, int height) {
    135             configureTransform(width, height);
    136         }
    137 
    138         @Override
    139         public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
    140             return true;
    141         }
    142 
    143         @Override
    144         public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
    145         }
    146 
    147     };
    148 
    149     /**
    150      * The {@link android.util.Size} of camera preview.
    151      */
    152     private Size mPreviewSize;
    153 
    154     /**
    155      * The {@link android.util.Size} of video recording.
    156      */
    157     private Size mVideoSize;
    158 
    159     /**
    160      * MediaRecorder
    161      */
    162     private MediaRecorder mMediaRecorder;
    163 
    164     /**
    165      * Whether the app is recording video now
    166      */
    167     private boolean mIsRecordingVideo;
    168 
    169     /**
    170      * An additional thread for running tasks that shouldn't block the UI.
    171      */
    172     private HandlerThread mBackgroundThread;
    173 
    174     /**
    175      * A {@link Handler} for running tasks in the background.
    176      */
    177     private Handler mBackgroundHandler;
    178 
    179     /**
    180      * A {@link Semaphore} to prevent the app from exiting before closing the camera.
    181      */
    182     private Semaphore mCameraOpenCloseLock = new Semaphore(1);
    183 
    184     /**
    185      * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its status.
    186      */
    187     private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
    188 
    189         @Override
    190         public void onOpened(CameraDevice cameraDevice) {
    191             mCameraDevice = cameraDevice;
    192             startPreview();
    193             mCameraOpenCloseLock.release();
    194             if (null != mTextureView) {
    195                 configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
    196             }
    197         }
    198 
    199         @Override
    200         public void onDisconnected(CameraDevice cameraDevice) {
    201             mCameraOpenCloseLock.release();
    202             cameraDevice.close();
    203             mCameraDevice = null;
    204         }
    205 
    206         @Override
    207         public void onError(CameraDevice cameraDevice, int error) {
    208             mCameraOpenCloseLock.release();
    209             cameraDevice.close();
    210             mCameraDevice = null;
    211             Activity activity = getActivity();
    212             if (null != activity) {
    213                 activity.finish();
    214             }
    215         }
    216 
    217     };
    218     private Integer mSensorOrientation;
    219     private String mNextVideoAbsolutePath;
    220     private CaptureRequest.Builder mPreviewBuilder;
    221     private Surface mRecorderSurface;
    222 
    223     public static Camera2VideoFragment newInstance() {
    224         return new Camera2VideoFragment();
    225     }
    226 
    227     /**
    228      * In this sample, we choose a video size with 3x4 aspect ratio. Also, we don't use sizes
    229      * larger than 1080p, since MediaRecorder cannot handle such a high-resolution video.
    230      *
    231      * @param choices The list of available sizes
    232      * @return The video size
    233      */
    234     private static Size chooseVideoSize(Size[] choices) {
    235         for (Size size : choices) {
    236             if (size.getWidth() == size.getHeight() * 4 / 3 && size.getWidth() <= 1080) {
    237                 return size;
    238             }
    239         }
    240         Log.e(TAG, "Couldn't find any suitable video size");
    241         return choices[choices.length - 1];
    242     }
    243 
    244     /**
    245      * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose
    246      * width and height are at least as large as the respective requested values, and whose aspect
    247      * ratio matches with the specified value.
    248      *
    249      * @param choices     The list of sizes that the camera supports for the intended output class
    250      * @param width       The minimum desired width
    251      * @param height      The minimum desired height
    252      * @param aspectRatio The aspect ratio
    253      * @return The optimal {@code Size}, or an arbitrary one if none were big enough
    254      */
    255     private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
    256         // Collect the supported resolutions that are at least as big as the preview Surface
    257         List<Size> bigEnough = new ArrayList<Size>();
    258         int w = aspectRatio.getWidth();
    259         int h = aspectRatio.getHeight();
    260         for (Size option : choices) {
    261             if (option.getHeight() == option.getWidth() * h / w &&
    262                     option.getWidth() >= width && option.getHeight() >= height) {
    263                 bigEnough.add(option);
    264             }
    265         }
    266 
    267         // Pick the smallest of those, assuming we found any
    268         if (bigEnough.size() > 0) {
    269             return Collections.min(bigEnough, new CompareSizesByArea());
    270         } else {
    271             Log.e(TAG, "Couldn't find any suitable preview size");
    272             return choices[0];
    273         }
    274     }
    275 
    276     @Override
    277     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    278                              Bundle savedInstanceState) {
    279         return inflater.inflate(R.layout.fragment_camera2_video, container, false);
    280     }
    281 
    282     @Override
    283     public void onViewCreated(final View view, Bundle savedInstanceState) {
    284         mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture);
    285         mButtonVideo = (Button) view.findViewById(R.id.video);
    286         mButtonVideo.setOnClickListener(this);
    287         view.findViewById(R.id.info).setOnClickListener(this);
    288     }
    289 
    290     @Override
    291     public void onResume() {
    292         super.onResume();
    293         startBackgroundThread();
    294         if (mTextureView.isAvailable()) {
    295             openCamera(mTextureView.getWidth(), mTextureView.getHeight());
    296         } else {
    297             mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
    298         }
    299     }
    300 
    301     @Override
    302     public void onPause() {
    303         closeCamera();
    304         stopBackgroundThread();
    305         super.onPause();
    306     }
    307 
    308     @Override
    309     public void onClick(View view) {
    310         switch (view.getId()) {
    311             case R.id.video: {
    312                 if (mIsRecordingVideo) {
    313                     stopRecordingVideo();
    314                 } else {
    315                     startRecordingVideo();
    316                 }
    317                 break;
    318             }
    319             case R.id.info: {
    320                 Activity activity = getActivity();
    321                 if (null != activity) {
    322                     new AlertDialog.Builder(activity)
    323                             .setMessage(R.string.intro_message)
    324                             .setPositiveButton(android.R.string.ok, null)
    325                             .show();
    326                 }
    327                 break;
    328             }
    329         }
    330     }
    331 
    332     /**
    333      * Starts a background thread and its {@link Handler}.
    334      */
    335     private void startBackgroundThread() {
    336         mBackgroundThread = new HandlerThread("CameraBackground");
    337         mBackgroundThread.start();
    338         mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    339     }
    340 
    341     /**
    342      * Stops the background thread and its {@link Handler}.
    343      */
    344     private void stopBackgroundThread() {
    345         mBackgroundThread.quitSafely();
    346         try {
    347             mBackgroundThread.join();
    348             mBackgroundThread = null;
    349             mBackgroundHandler = null;
    350         } catch (InterruptedException e) {
    351             e.printStackTrace();
    352         }
    353     }
    354 
    355     /**
    356      * Gets whether you should show UI with rationale for requesting permissions.
    357      *
    358      * @param permissions The permissions your app wants to request.
    359      * @return Whether you can show permission rationale UI.
    360      */
    361     private boolean shouldShowRequestPermissionRationale(String[] permissions) {
    362         for (String permission : permissions) {
    363             if (FragmentCompat.shouldShowRequestPermissionRationale(this, permission)) {
    364                 return true;
    365             }
    366         }
    367         return false;
    368     }
    369 
    370     /**
    371      * Requests permissions needed for recording video.
    372      */
    373     private void requestVideoPermissions() {
    374         if (shouldShowRequestPermissionRationale(VIDEO_PERMISSIONS)) {
    375             new ConfirmationDialog().show(getChildFragmentManager(), FRAGMENT_DIALOG);
    376         } else {
    377             FragmentCompat.requestPermissions(this, VIDEO_PERMISSIONS, REQUEST_VIDEO_PERMISSIONS);
    378         }
    379     }
    380 
    381     @Override
    382     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
    383                                            @NonNull int[] grantResults) {
    384         Log.d(TAG, "onRequestPermissionsResult");
    385         if (requestCode == REQUEST_VIDEO_PERMISSIONS) {
    386             if (grantResults.length == VIDEO_PERMISSIONS.length) {
    387                 for (int result : grantResults) {
    388                     if (result != PackageManager.PERMISSION_GRANTED) {
    389                         ErrorDialog.newInstance(getString(R.string.permission_request))
    390                                 .show(getChildFragmentManager(), FRAGMENT_DIALOG);
    391                         break;
    392                     }
    393                 }
    394             } else {
    395                 ErrorDialog.newInstance(getString(R.string.permission_request))
    396                         .show(getChildFragmentManager(), FRAGMENT_DIALOG);
    397             }
    398         } else {
    399             super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    400         }
    401     }
    402 
    403     private boolean hasPermissionsGranted(String[] permissions) {
    404         for (String permission : permissions) {
    405             if (ActivityCompat.checkSelfPermission(getActivity(), permission)
    406                     != PackageManager.PERMISSION_GRANTED) {
    407                 return false;
    408             }
    409         }
    410         return true;
    411     }
    412 
    413     /**
    414      * Tries to open a {@link CameraDevice}. The result is listened by `mStateCallback`.
    415      */
    416     private void openCamera(int width, int height) {
    417         if (!hasPermissionsGranted(VIDEO_PERMISSIONS)) {
    418             requestVideoPermissions();
    419             return;
    420         }
    421         final Activity activity = getActivity();
    422         if (null == activity || activity.isFinishing()) {
    423             return;
    424         }
    425         CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
    426         try {
    427             Log.d(TAG, "tryAcquire");
    428             if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
    429                 throw new RuntimeException("Time out waiting to lock camera opening.");
    430             }
    431             String cameraId = manager.getCameraIdList()[0];
    432 
    433             // Choose the sizes for camera preview and video recording
    434             CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
    435             StreamConfigurationMap map = characteristics
    436                     .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    437             mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
    438             mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class));
    439             mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
    440                     width, height, mVideoSize);
    441 
    442             int orientation = getResources().getConfiguration().orientation;
    443             if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
    444                 mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());
    445             } else {
    446                 mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth());
    447             }
    448             configureTransform(width, height);
    449             mMediaRecorder = new MediaRecorder();
    450             manager.openCamera(cameraId, mStateCallback, null);
    451         } catch (CameraAccessException e) {
    452             Toast.makeText(activity, "Cannot access the camera.", Toast.LENGTH_SHORT).show();
    453             activity.finish();
    454         } catch (NullPointerException e) {
    455             // Currently an NPE is thrown when the Camera2API is used but not supported on the
    456             // device this code runs.
    457             ErrorDialog.newInstance(getString(R.string.camera_error))
    458                     .show(getChildFragmentManager(), FRAGMENT_DIALOG);
    459         } catch (InterruptedException e) {
    460             throw new RuntimeException("Interrupted while trying to lock camera opening.");
    461         }
    462     }
    463 
    464     private void closeCamera() {
    465         try {
    466             mCameraOpenCloseLock.acquire();
    467             closePreviewSession();
    468             if (null != mCameraDevice) {
    469                 mCameraDevice.close();
    470                 mCameraDevice = null;
    471             }
    472             if (null != mMediaRecorder) {
    473                 mMediaRecorder.release();
    474                 mMediaRecorder = null;
    475             }
    476         } catch (InterruptedException e) {
    477             throw new RuntimeException("Interrupted while trying to lock camera closing.");
    478         } finally {
    479             mCameraOpenCloseLock.release();
    480         }
    481     }
    482 
    483     /**
    484      * Start the camera preview.
    485      */
    486     private void startPreview() {
    487         if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
    488             return;
    489         }
    490         try {
    491             closePreviewSession();
    492             SurfaceTexture texture = mTextureView.getSurfaceTexture();
    493             assert texture != null;
    494             texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
    495             mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    496 
    497             Surface previewSurface = new Surface(texture);
    498             mPreviewBuilder.addTarget(previewSurface);
    499 
    500             mCameraDevice.createCaptureSession(Arrays.asList(previewSurface), new CameraCaptureSession.StateCallback() {
    501 
    502                 @Override
    503                 public void onConfigured(CameraCaptureSession cameraCaptureSession) {
    504                     mPreviewSession = cameraCaptureSession;
    505                     updatePreview();
    506                 }
    507 
    508                 @Override
    509                 public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
    510                     Activity activity = getActivity();
    511                     if (null != activity) {
    512                         Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show();
    513                     }
    514                 }
    515             }, mBackgroundHandler);
    516         } catch (CameraAccessException e) {
    517             e.printStackTrace();
    518         }
    519     }
    520 
    521     /**
    522      * Update the camera preview. {@link #startPreview()} needs to be called in advance.
    523      */
    524     private void updatePreview() {
    525         if (null == mCameraDevice) {
    526             return;
    527         }
    528         try {
    529             setUpCaptureRequestBuilder(mPreviewBuilder);
    530             HandlerThread thread = new HandlerThread("CameraPreview");
    531             thread.start();
    532             mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler);
    533         } catch (CameraAccessException e) {
    534             e.printStackTrace();
    535         }
    536     }
    537 
    538     private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) {
    539         builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
    540     }
    541 
    542     /**
    543      * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`.
    544      * This method should not to be called until the camera preview size is determined in
    545      * openCamera, or until the size of `mTextureView` is fixed.
    546      *
    547      * @param viewWidth  The width of `mTextureView`
    548      * @param viewHeight The height of `mTextureView`
    549      */
    550     private void configureTransform(int viewWidth, int viewHeight) {
    551         Activity activity = getActivity();
    552         if (null == mTextureView || null == mPreviewSize || null == activity) {
    553             return;
    554         }
    555         int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
    556         Matrix matrix = new Matrix();
    557         RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
    558         RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
    559         float centerX = viewRect.centerX();
    560         float centerY = viewRect.centerY();
    561         if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
    562             bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
    563             matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
    564             float scale = Math.max(
    565                     (float) viewHeight / mPreviewSize.getHeight(),
    566                     (float) viewWidth / mPreviewSize.getWidth());
    567             matrix.postScale(scale, scale, centerX, centerY);
    568             matrix.postRotate(90 * (rotation - 2), centerX, centerY);
    569         }
    570         mTextureView.setTransform(matrix);
    571     }
    572 
    573     private void setUpMediaRecorder() throws IOException {
    574         final Activity activity = getActivity();
    575         if (null == activity) {
    576             return;
    577         }
    578         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    579         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
    580         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
    581         if (mNextVideoAbsolutePath == null || mNextVideoAbsolutePath.isEmpty()) {
    582             mNextVideoAbsolutePath = getVideoFilePath(getActivity());
    583         }
    584         mMediaRecorder.setOutputFile(mNextVideoAbsolutePath);
    585         mMediaRecorder.setVideoEncodingBitRate(10000000);
    586         mMediaRecorder.setVideoFrameRate(30);
    587         mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
    588         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
    589         mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
    590         int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
    591         switch (mSensorOrientation) {
    592             case SENSOR_ORIENTATION_DEFAULT_DEGREES:
    593                 mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));
    594                 break;
    595             case SENSOR_ORIENTATION_INVERSE_DEGREES:
    596                 mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation));
    597                 break;
    598         }
    599         mMediaRecorder.prepare();
    600     }
    601 
    602     private String getVideoFilePath(Context context) {
    603         return context.getExternalFilesDir(null).getAbsolutePath() + "/"
    604                 + System.currentTimeMillis() + ".mp4";
    605     }
    606 
    607     private void startRecordingVideo() {
    608         if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
    609             return;
    610         }
    611         try {
    612             closePreviewSession();
    613             setUpMediaRecorder();
    614             SurfaceTexture texture = mTextureView.getSurfaceTexture();
    615             assert texture != null;
    616             texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
    617             mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
    618             List<Surface> surfaces = new ArrayList<>();
    619 
    620             // Set up Surface for the camera preview
    621             Surface previewSurface = new Surface(texture);
    622             surfaces.add(previewSurface);
    623             mPreviewBuilder.addTarget(previewSurface);
    624 
    625             // Set up Surface for the MediaRecorder
    626             mRecorderSurface = mMediaRecorder.getSurface();
    627             surfaces.add(mRecorderSurface);
    628             mPreviewBuilder.addTarget(mRecorderSurface);
    629 
    630             // Start a capture session
    631             // Once the session starts, we can update the UI and start recording
    632             mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
    633 
    634                 @Override
    635                 public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
    636                     mPreviewSession = cameraCaptureSession;
    637                     updatePreview();
    638                     getActivity().runOnUiThread(new Runnable() {
    639                         @Override
    640                         public void run() {
    641                             // UI
    642                             mButtonVideo.setText(R.string.stop);
    643                             mIsRecordingVideo = true;
    644 
    645                             // Start recording
    646                             mMediaRecorder.start();
    647                         }
    648                     });
    649                 }
    650 
    651                 @Override
    652                 public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
    653                     Activity activity = getActivity();
    654                     if (null != activity) {
    655                         Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show();
    656                     }
    657                 }
    658             }, mBackgroundHandler);
    659         } catch (CameraAccessException e) {
    660             e.printStackTrace();
    661         } catch (IOException e) {
    662             e.printStackTrace();
    663         }
    664 
    665     }
    666 
    667     private void closePreviewSession() {
    668         if (mPreviewSession != null) {
    669             mPreviewSession.close();
    670             mPreviewSession = null;
    671         }
    672     }
    673 
    674     private void stopRecordingVideo() {
    675         // UI
    676         mIsRecordingVideo = false;
    677         mButtonVideo.setText(R.string.record);
    678         // Stop recording
    679         mMediaRecorder.stop();
    680         mMediaRecorder.reset();
    681 
    682         Activity activity = getActivity();
    683         if (null != activity) {
    684             Toast.makeText(activity, "Video saved: " + mNextVideoAbsolutePath,
    685                     Toast.LENGTH_SHORT).show();
    686             Log.d(TAG, "Video saved: " + mNextVideoAbsolutePath);
    687         }
    688         mNextVideoAbsolutePath = null;
    689         startPreview();
    690     }
    691 
    692     /**
    693      * Compares two {@code Size}s based on their areas.
    694      */
    695     static class CompareSizesByArea implements Comparator<Size> {
    696 
    697         @Override
    698         public int compare(Size lhs, Size rhs) {
    699             // We cast here to ensure the multiplications won't overflow
    700             return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
    701                     (long) rhs.getWidth() * rhs.getHeight());
    702         }
    703 
    704     }
    705 
    706     public static class ErrorDialog extends DialogFragment {
    707 
    708         private static final String ARG_MESSAGE = "message";
    709 
    710         public static ErrorDialog newInstance(String message) {
    711             ErrorDialog dialog = new ErrorDialog();
    712             Bundle args = new Bundle();
    713             args.putString(ARG_MESSAGE, message);
    714             dialog.setArguments(args);
    715             return dialog;
    716         }
    717 
    718         @Override
    719         public Dialog onCreateDialog(Bundle savedInstanceState) {
    720             final Activity activity = getActivity();
    721             return new AlertDialog.Builder(activity)
    722                     .setMessage(getArguments().getString(ARG_MESSAGE))
    723                     .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
    724                         @Override
    725                         public void onClick(DialogInterface dialogInterface, int i) {
    726                             activity.finish();
    727                         }
    728                     })
    729                     .create();
    730         }
    731 
    732     }
    733 
    734     public static class ConfirmationDialog extends DialogFragment {
    735 
    736         @Override
    737         public Dialog onCreateDialog(Bundle savedInstanceState) {
    738             final Fragment parent = getParentFragment();
    739             return new AlertDialog.Builder(getActivity())
    740                     .setMessage(R.string.permission_request)
    741                     .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
    742                         @Override
    743                         public void onClick(DialogInterface dialog, int which) {
    744                             FragmentCompat.requestPermissions(parent, VIDEO_PERMISSIONS,
    745                                     REQUEST_VIDEO_PERMISSIONS);
    746                         }
    747                     })
    748                     .setNegativeButton(android.R.string.cancel,
    749                             new DialogInterface.OnClickListener() {
    750                                 @Override
    751                                 public void onClick(DialogInterface dialog, int which) {
    752                                     parent.getActivity().finish();
    753                                 }
    754                             })
    755                     .create();
    756         }
    757 
    758     }
    759 
    760 }
    761