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