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