1 /*
2  * Copyright 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *       http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.powerprofile.cameraavg;
18 
19 import android.Manifest;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.app.DialogFragment;
24 import android.app.Fragment;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.pm.PackageManager;
28 import android.content.res.Configuration;
29 import android.graphics.ImageFormat;
30 import android.graphics.Matrix;
31 import android.graphics.Point;
32 import android.graphics.RectF;
33 import android.graphics.SurfaceTexture;
34 import android.hardware.camera2.CameraAccessException;
35 import android.hardware.camera2.CameraCaptureSession;
36 import android.hardware.camera2.CameraCharacteristics;
37 import android.hardware.camera2.CameraDevice;
38 import android.hardware.camera2.CameraManager;
39 import android.hardware.camera2.CameraMetadata;
40 import android.hardware.camera2.CaptureRequest;
41 import android.hardware.camera2.CaptureResult;
42 import android.hardware.camera2.TotalCaptureResult;
43 import android.hardware.camera2.params.StreamConfigurationMap;
44 import android.media.Image;
45 import android.media.ImageReader;
46 import android.os.Bundle;
47 import android.os.Handler;
48 import android.os.HandlerThread;
49 import android.support.annotation.NonNull;
50 import android.support.v13.app.FragmentCompat;
51 import android.support.v4.content.ContextCompat;
52 import android.util.Log;
53 import android.util.Size;
54 import android.util.SparseIntArray;
55 import android.view.LayoutInflater;
56 import android.view.Surface;
57 import android.view.TextureView;
58 import android.view.View;
59 import android.view.ViewGroup;
60 import android.widget.Toast;
61 
62 import java.io.File;
63 import java.io.FileOutputStream;
64 import java.io.IOException;
65 import java.nio.ByteBuffer;
66 import java.util.ArrayList;
67 import java.util.Arrays;
68 import java.util.Collections;
69 import java.util.Comparator;
70 import java.util.List;
71 import java.util.concurrent.Semaphore;
72 import java.util.concurrent.TimeUnit;
73 
74 public class CameraAvgFragment extends Fragment
75         implements View.OnClickListener, FragmentCompat.OnRequestPermissionsResultCallback {
76 
77     /**
78      * Conversion from screen rotation to JPEG orientation.
79      */
80     private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
81     private static final int REQUEST_CAMERA_PERMISSION = 1;
82     private static final String FRAGMENT_DIALOG = "dialog";
83 
84     static {
ORIENTATIONS.append(Surface.ROTATION_0, 90)85         ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0)86         ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270)87         ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180)88         ORIENTATIONS.append(Surface.ROTATION_270, 180);
89     }
90 
91     /**
92      * Tag for the {@link Log}.
93      */
94     private static final String TAG = "CameraAvgFragment";
95 
96     /**
97      * Camera state: Showing camera preview.
98      */
99     private static final int STATE_PREVIEW = 0;
100 
101     /**
102      * Camera state: Waiting for the focus to be locked.
103      */
104     private static final int STATE_WAITING_LOCK = 1;
105 
106     /**
107      * Camera state: Waiting for the exposure to be precapture state.
108      */
109     private static final int STATE_WAITING_PRECAPTURE = 2;
110 
111     /**
112      * Camera state: Waiting for the exposure state to be something other than precapture.
113      */
114     private static final int STATE_WAITING_NON_PRECAPTURE = 3;
115 
116     /**
117      * Camera state: Picture was taken.
118      */
119     private static final int STATE_PICTURE_TAKEN = 4;
120 
121     /**
122      * Max preview width that is guaranteed by Camera2 API
123      */
124     private static final int MAX_PREVIEW_WIDTH = 1920;
125 
126     /**
127      * Max preview height that is guaranteed by Camera2 API
128      */
129     private static final int MAX_PREVIEW_HEIGHT = 1080;
130 
131     /**
132      * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a
133      * {@link TextureView}.
134      */
135     private final TextureView.SurfaceTextureListener mSurfaceTextureListener
136             = new TextureView.SurfaceTextureListener() {
137 
138         @Override
139         public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
140             openCamera(width, height);
141         }
142 
143         @Override
144         public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
145             configureTransform(width, height);
146         }
147 
148         @Override
149         public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
150             return true;
151         }
152 
153         @Override
154         public void onSurfaceTextureUpdated(SurfaceTexture texture) {
155         }
156 
157     };
158 
159     /**
160      * ID of the current {@link CameraDevice}.
161      */
162     private String mCameraId;
163 
164     /**
165      * An {@link AutoFitTextureView} for camera preview.
166      */
167     private AutoFitTextureView mTextureView;
168 
169     /**
170      * A {@link CameraCaptureSession } for camera preview.
171      */
172     private CameraCaptureSession mCaptureSession;
173 
174     /**
175      * A reference to the opened {@link CameraDevice}.
176      */
177     private CameraDevice mCameraDevice;
178 
179     /**
180      * The {@link Size} of camera preview.
181      */
182     private Size mPreviewSize;
183 
184     private Runnable mCaptureRunnable;
185 
186     /**
187      * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.
188      */
189     private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
190 
191         @Override
192         public void onOpened(@NonNull CameraDevice cameraDevice) {
193             // This method is called when the camera is opened.  We start camera preview here.
194             mCameraOpenCloseLock.release();
195             mCameraDevice = cameraDevice;
196             createCameraPreviewSession();
197         }
198 
199         @Override
200         public void onDisconnected(@NonNull CameraDevice cameraDevice) {
201             mCameraOpenCloseLock.release();
202             cameraDevice.close();
203             mCameraDevice = null;
204         }
205 
206         @Override
207         public void onError(@NonNull CameraDevice cameraDevice, int error) {
208             mCameraOpenCloseLock.release();
209             cameraDevice.close();
210             mCameraDevice = null;
211             Activity activity = getActivity();
212             if (null != activity && !activity.isFinishing()) {
213                 activity.finish();
214             }
215         }
216 
217     };
218 
219     /**
220      * An additional thread for running tasks that shouldn't block the UI.
221      */
222     private HandlerThread mBackgroundThread;
223 
224     /**
225      * A {@link Handler} for running tasks in the background.
226      */
227     private Handler mBackgroundHandler;
228 
229     /**
230      * An {@link ImageReader} that handles still image capture.
231      */
232     private ImageReader mImageReader;
233 
234     /**
235      * This is the output file for our picture.
236      */
237     private File mFile;
238 
239     /**
240      * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
241      * still image is ready to be saved.
242      */
243     private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
244             = new ImageReader.OnImageAvailableListener() {
245 
246         @Override
247         public void onImageAvailable(ImageReader reader) {
248             mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
249         }
250     };
251 
252     /**
253      * {@link CaptureRequest.Builder} for the camera preview
254      */
255     private CaptureRequest.Builder mPreviewRequestBuilder;
256 
257     /**
258      * {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder}
259      */
260     private CaptureRequest mPreviewRequest;
261 
262     /**
263      * The current state of camera state for taking pictures.
264      *
265      * @see #mCaptureCallback
266      */
267     private int mState = STATE_PREVIEW;
268 
269     /**
270      * A {@link Semaphore} to prevent the app from exiting before closing the camera.
271      */
272     private Semaphore mCameraOpenCloseLock = new Semaphore(1);
273 
274     /**
275      * Whether the current camera device supports Flash or not.
276      */
277     private boolean mFlashSupported;
278 
279     /**
280      * Orientation of the camera sensor
281      */
282     private int mSensorOrientation;
283 
284     /**
285      * A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture.
286      */
287     private CameraCaptureSession.CaptureCallback mCaptureCallback
288             = new CameraCaptureSession.CaptureCallback() {
289 
290         private void process(CaptureResult result) {
291             switch (mState) {
292                 case STATE_PREVIEW: {
293                     // We have nothing to do when the camera preview is working normally.
294                     break;
295                 }
296                 case STATE_WAITING_LOCK: {
297                     Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
298                     if (afState == null) {
299                         captureStillPicture();
300                     } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
301                             CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
302                         // CONTROL_AE_STATE can be null on some devices
303                         Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
304                         if (aeState == null ||
305                                 aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
306                             mState = STATE_PICTURE_TAKEN;
307                             captureStillPicture();
308                         } else {
309                             runPrecaptureSequence();
310                         }
311                     }
312                     break;
313                 }
314                 case STATE_WAITING_PRECAPTURE: {
315                     // CONTROL_AE_STATE can be null on some devices
316                     Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
317                     if (aeState == null ||
318                             aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
319                             aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
320                         mState = STATE_WAITING_NON_PRECAPTURE;
321                     }
322                     break;
323                 }
324                 case STATE_WAITING_NON_PRECAPTURE: {
325                     // CONTROL_AE_STATE can be null on some devices
326                     Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
327                     if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
328                         mState = STATE_PICTURE_TAKEN;
329                         captureStillPicture();
330                     }
331                     break;
332                 }
333             }
334         }
335 
336         @Override
337         public void onCaptureProgressed(@NonNull CameraCaptureSession session,
338                                         @NonNull CaptureRequest request,
339                                         @NonNull CaptureResult partialResult) {
340             process(partialResult);
341         }
342 
343         @Override
344         public void onCaptureCompleted(@NonNull CameraCaptureSession session,
345                                        @NonNull CaptureRequest request,
346                                        @NonNull TotalCaptureResult result) {
347             process(result);
348         }
349 
350     };
351 
352     /**
353      * Shows a {@link Toast} on the UI thread.
354      *
355      * @param text The message to show
356      */
showToast(final String text)357     private void showToast(final String text) {
358         final Activity activity = getActivity();
359         if (activity != null) {
360             activity.runOnUiThread(new Runnable() {
361                 @Override
362                 public void run() {
363                     Toast.makeText(activity, text, Toast.LENGTH_SHORT).show();
364                 }
365             });
366         }
367     }
368 
369     /**
370      * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that
371      * is at least as large as the respective texture view size, and that is at most as large as the
372      * respective max size, and whose aspect ratio matches with the specified value. If such size
373      * doesn't exist, choose the largest one that is at most as large as the respective max size,
374      * and whose aspect ratio matches with the specified value.
375      *
376      * @param choices           The list of sizes that the camera supports for the intended output
377      *                          class
378      * @param textureViewWidth  The width of the texture view relative to sensor coordinate
379      * @param textureViewHeight The height of the texture view relative to sensor coordinate
380      * @param maxWidth          The maximum width that can be chosen
381      * @param maxHeight         The maximum height that can be chosen
382      * @param aspectRatio       The aspect ratio
383      * @return The optimal {@code Size}, or an arbitrary one if none were big enough
384      */
chooseOptimalSize(Size[] choices, int textureViewWidth, int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio)385     private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
386             int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
387 
388         // Collect the supported resolutions that are at least as big as the preview Surface
389         List<Size> bigEnough = new ArrayList<>();
390         // Collect the supported resolutions that are smaller than the preview Surface
391         List<Size> notBigEnough = new ArrayList<>();
392         int w = aspectRatio.getWidth();
393         int h = aspectRatio.getHeight();
394         for (Size option : choices) {
395             if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
396                     option.getHeight() == option.getWidth() * h / w) {
397                 if (option.getWidth() >= textureViewWidth &&
398                     option.getHeight() >= textureViewHeight) {
399                     bigEnough.add(option);
400                 } else {
401                     notBigEnough.add(option);
402                 }
403             }
404         }
405 
406         // Pick the smallest of those big enough. If there is no one big enough, pick the
407         // largest of those not big enough.
408         if (bigEnough.size() > 0) {
409             return Collections.min(bigEnough, new CompareSizesByArea());
410         } else if (notBigEnough.size() > 0) {
411             return Collections.max(notBigEnough, new CompareSizesByArea());
412         } else {
413             Log.e(TAG, "Couldn't find any suitable preview size");
414             return choices[0];
415         }
416     }
417 
newInstance()418     public static CameraAvgFragment newInstance() {
419         return new CameraAvgFragment();
420     }
421 
422     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)423     public View onCreateView(LayoutInflater inflater, ViewGroup container,
424                              Bundle savedInstanceState) {
425         return inflater.inflate(R.layout.fragment_camera_avg, container, false);
426     }
427 
428     @Override
onViewCreated(final View view, Bundle savedInstanceState)429     public void onViewCreated(final View view, Bundle savedInstanceState) {
430         view.findViewById(R.id.picture).setOnClickListener(this);
431         view.findViewById(R.id.info).setOnClickListener(this);
432         mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture);
433     }
434 
435     @Override
onActivityCreated(Bundle savedInstanceState)436     public void onActivityCreated(Bundle savedInstanceState) {
437         super.onActivityCreated(savedInstanceState);
438         mFile = new File(getActivity().getExternalFilesDir(null), "pic.jpg");
439     }
440 
441     @Override
onResume()442     public void onResume() {
443         super.onResume();
444         startBackgroundThread();
445 
446         // When the screen is turned off and turned back on, the SurfaceTexture is already
447         // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
448         // a camera and start preview from here (otherwise, we wait until the surface is ready in
449         // the SurfaceTextureListener).
450         if (mTextureView.isAvailable()) {
451             openCamera(mTextureView.getWidth(), mTextureView.getHeight());
452         } else {
453             mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
454         }
455 
456         mCaptureRunnable = new Runnable() {
457             public void run() {
458                 mBackgroundHandler.postDelayed(this, 6000);
459                 CameraAvgFragment.this.takePicture();
460             }
461         };
462         mBackgroundHandler.postDelayed(mCaptureRunnable, 6000);
463     }
464 
465     @Override
onPause()466     public void onPause() {
467         mBackgroundHandler.removeCallbacks(mCaptureRunnable);
468 
469         closeCamera();
470         stopBackgroundThread();
471         super.onPause();
472     }
473 
requestCameraPermission()474     private void requestCameraPermission() {
475         if (FragmentCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
476             new ConfirmationDialog().show(getChildFragmentManager(), FRAGMENT_DIALOG);
477         } else {
478             FragmentCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
479                     REQUEST_CAMERA_PERMISSION);
480         }
481     }
482 
483     @Override
onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)484     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
485                                            @NonNull int[] grantResults) {
486         if (requestCode == REQUEST_CAMERA_PERMISSION) {
487             if (grantResults.length != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
488                 ErrorDialog.newInstance(getString(R.string.request_permission))
489                         .show(getChildFragmentManager(), FRAGMENT_DIALOG);
490             }
491         } else {
492             super.onRequestPermissionsResult(requestCode, permissions, grantResults);
493         }
494     }
495 
496     /**
497      * Sets up member variables related to camera.
498      *
499      * @param width  The width of available size for camera preview
500      * @param height The height of available size for camera preview
501      */
setUpCameraOutputs(int width, int height)502     private void setUpCameraOutputs(int width, int height) {
503         Activity activity = getActivity();
504         CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
505         try {
506             for (String cameraId : manager.getCameraIdList()) {
507                 CameraCharacteristics characteristics
508                         = manager.getCameraCharacteristics(cameraId);
509 
510                 // We don't use a front facing camera in this sample.
511                 Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
512                 if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
513                     continue;
514                 }
515 
516                 StreamConfigurationMap map = characteristics.get(
517                         CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
518                 if (map == null) {
519                     continue;
520                 }
521 
522                 // For still image captures, we use the largest available size.
523                 Size largest = Collections.max(
524                         Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
525                         new CompareSizesByArea());
526                 mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
527                         ImageFormat.JPEG, /*maxImages*/2);
528                 mImageReader.setOnImageAvailableListener(
529                         mOnImageAvailableListener, mBackgroundHandler);
530 
531                 // Find out if we need to swap dimension to get the preview size relative to sensor
532                 // coordinate.
533                 int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
534                 //noinspection ConstantConditions
535                 mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
536                 boolean swappedDimensions = false;
537                 switch (displayRotation) {
538                     case Surface.ROTATION_0:
539                     case Surface.ROTATION_180:
540                         if (mSensorOrientation == 90 || mSensorOrientation == 270) {
541                             swappedDimensions = true;
542                         }
543                         break;
544                     case Surface.ROTATION_90:
545                     case Surface.ROTATION_270:
546                         if (mSensorOrientation == 0 || mSensorOrientation == 180) {
547                             swappedDimensions = true;
548                         }
549                         break;
550                     default:
551                         Log.e(TAG, "Display rotation is invalid: " + displayRotation);
552                 }
553 
554                 Point displaySize = new Point();
555                 activity.getWindowManager().getDefaultDisplay().getSize(displaySize);
556                 int rotatedPreviewWidth = width;
557                 int rotatedPreviewHeight = height;
558                 int maxPreviewWidth = displaySize.x;
559                 int maxPreviewHeight = displaySize.y;
560 
561                 if (swappedDimensions) {
562                     rotatedPreviewWidth = height;
563                     rotatedPreviewHeight = width;
564                     maxPreviewWidth = displaySize.y;
565                     maxPreviewHeight = displaySize.x;
566                 }
567 
568                 if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
569                     maxPreviewWidth = MAX_PREVIEW_WIDTH;
570                 }
571 
572                 if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
573                     maxPreviewHeight = MAX_PREVIEW_HEIGHT;
574                 }
575 
576                 // Danger, W.R.! Attempting to use too large a preview size could exceed the camera
577                 // bus' bandwidth limitation, resulting in gorgeous previews but the storage of
578                 // garbage capture data.
579                 mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
580                         rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
581                         maxPreviewHeight, largest);
582 
583                 // We fit the aspect ratio of TextureView to the size of preview we picked.
584                 int orientation = getResources().getConfiguration().orientation;
585                 if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
586                     mTextureView.setAspectRatio(
587                             mPreviewSize.getWidth(), mPreviewSize.getHeight());
588                 } else {
589                     mTextureView.setAspectRatio(
590                             mPreviewSize.getHeight(), mPreviewSize.getWidth());
591                 }
592 
593                 // Check if the flash is supported.
594                 Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
595                 mFlashSupported = available == null ? false : available;
596 
597                 mCameraId = cameraId;
598                 return;
599             }
600         } catch (CameraAccessException e) {
601             e.printStackTrace();
602         } catch (NullPointerException e) {
603             // Currently an NPE is thrown when the Camera2API is used but not supported on the
604             // device this code runs.
605             ErrorDialog.newInstance(getString(R.string.camera_error))
606                     .show(getChildFragmentManager(), FRAGMENT_DIALOG);
607         }
608     }
609 
610     /**
611      * Opens the camera specified by {@link CameraAvgFragment#mCameraId}.
612      */
openCamera(int width, int height)613     private void openCamera(int width, int height) {
614         if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
615                 != PackageManager.PERMISSION_GRANTED) {
616             requestCameraPermission();
617             return;
618         }
619         setUpCameraOutputs(width, height);
620         configureTransform(width, height);
621         Activity activity = getActivity();
622         CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
623         try {
624             if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
625                 throw new RuntimeException("Time out waiting to lock camera opening.");
626             }
627             manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
628         } catch (CameraAccessException e) {
629             e.printStackTrace();
630         } catch (InterruptedException e) {
631             throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
632         }
633     }
634 
635     /**
636      * Closes the current {@link CameraDevice}.
637      */
closeCamera()638     private void closeCamera() {
639         try {
640             mCameraOpenCloseLock.acquire();
641             if (null != mCaptureSession) {
642                 mCaptureSession.close();
643                 mCaptureSession = null;
644             }
645             if (null != mCameraDevice) {
646                 mCameraDevice.close();
647                 mCameraDevice = null;
648             }
649             if (null != mImageReader) {
650                 mImageReader.close();
651                 mImageReader = null;
652             }
653         } catch (InterruptedException e) {
654             throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
655         } finally {
656             mCameraOpenCloseLock.release();
657         }
658     }
659 
660     /**
661      * Starts a background thread and its {@link Handler}.
662      */
startBackgroundThread()663     private void startBackgroundThread() {
664         mBackgroundThread = new HandlerThread("CameraBackground");
665         mBackgroundThread.start();
666         mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
667     }
668 
669     /**
670      * Stops the background thread and its {@link Handler}.
671      */
stopBackgroundThread()672     private void stopBackgroundThread() {
673         mBackgroundThread.quitSafely();
674         try {
675             mBackgroundThread.join();
676             mBackgroundThread = null;
677             mBackgroundHandler = null;
678         } catch (InterruptedException e) {
679             e.printStackTrace();
680         }
681     }
682 
683     /**
684      * Creates a new {@link CameraCaptureSession} for camera preview.
685      */
createCameraPreviewSession()686     private void createCameraPreviewSession() {
687         try {
688             SurfaceTexture texture = mTextureView.getSurfaceTexture();
689             assert texture != null;
690 
691             // We configure the size of default buffer to be the size of camera preview we want.
692             texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
693 
694             // This is the output Surface we need to start preview.
695             Surface surface = new Surface(texture);
696 
697             // We set up a CaptureRequest.Builder with the output Surface.
698             mPreviewRequestBuilder
699                     = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
700             mPreviewRequestBuilder.addTarget(surface);
701 
702             // Here, we create a CameraCaptureSession for camera preview.
703             mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
704                     new CameraCaptureSession.StateCallback() {
705 
706                         @Override
707                         public void onConfigured(
708                                 @NonNull CameraCaptureSession cameraCaptureSession) {
709                             // The camera is already closed
710                             if (null == mCameraDevice) {
711                                 return;
712                             }
713 
714                             // When the session is ready, we start displaying the preview.
715                             mCaptureSession = cameraCaptureSession;
716                             try {
717                                 // Auto focus should be continuous for camera preview.
718                                 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
719                                         CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
720                                 // Flash is automatically enabled when necessary.
721                                 setAutoFlash(mPreviewRequestBuilder);
722 
723                                 // Finally, we start displaying the camera preview.
724                                 mPreviewRequest = mPreviewRequestBuilder.build();
725                                 mCaptureSession.setRepeatingRequest(mPreviewRequest,
726                                         mCaptureCallback, mBackgroundHandler);
727                             } catch (CameraAccessException e) {
728                                 e.printStackTrace();
729                             }
730                         }
731 
732                         @Override
733                         public void onConfigureFailed(
734                                 @NonNull CameraCaptureSession cameraCaptureSession) {
735                             showToast("Failed");
736                         }
737                     }, null
738             );
739         } catch (CameraAccessException e) {
740             e.printStackTrace();
741         }
742     }
743 
744     /**
745      * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`.
746      * This method should be called after the camera preview size is determined in
747      * setUpCameraOutputs and also the size of `mTextureView` is fixed.
748      *
749      * @param viewWidth  The width of `mTextureView`
750      * @param viewHeight The height of `mTextureView`
751      */
configureTransform(int viewWidth, int viewHeight)752     private void configureTransform(int viewWidth, int viewHeight) {
753         Activity activity = getActivity();
754         if (null == mTextureView || null == mPreviewSize || null == activity) {
755             return;
756         }
757         int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
758         Matrix matrix = new Matrix();
759         RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
760         RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
761         float centerX = viewRect.centerX();
762         float centerY = viewRect.centerY();
763         if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
764             bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
765             matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
766             float scale = Math.max(
767                     (float) viewHeight / mPreviewSize.getHeight(),
768                     (float) viewWidth / mPreviewSize.getWidth());
769             matrix.postScale(scale, scale, centerX, centerY);
770             matrix.postRotate(90 * (rotation - 2), centerX, centerY);
771         } else if (Surface.ROTATION_180 == rotation) {
772             matrix.postRotate(180, centerX, centerY);
773         }
774         mTextureView.setTransform(matrix);
775     }
776 
777     /**
778      * Initiate a still image capture.
779      */
takePicture()780     private void takePicture() {
781         lockFocus();
782     }
783 
784     /**
785      * Lock the focus as the first step for a still image capture.
786      */
lockFocus()787     private void lockFocus() {
788         try {
789             // This is how to tell the camera to lock focus.
790             mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
791                     CameraMetadata.CONTROL_AF_TRIGGER_START);
792             // Tell #mCaptureCallback to wait for the lock.
793             mState = STATE_WAITING_LOCK;
794             mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
795                     mBackgroundHandler);
796         } catch (CameraAccessException e) {
797             e.printStackTrace();
798         }
799     }
800 
801     /**
802      * Run the precapture sequence for capturing a still image. This method should be called when
803      * we get a response in {@link #mCaptureCallback} from {@link #lockFocus()}.
804      */
runPrecaptureSequence()805     private void runPrecaptureSequence() {
806         try {
807             // This is how to tell the camera to trigger.
808             mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
809                     CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
810             // Tell #mCaptureCallback to wait for the precapture sequence to be set.
811             mState = STATE_WAITING_PRECAPTURE;
812             mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
813                     mBackgroundHandler);
814         } catch (CameraAccessException e) {
815             e.printStackTrace();
816         }
817     }
818 
819     /**
820      * Capture a still picture. This method should be called when we get a response in
821      * {@link #mCaptureCallback} from {@link #lockFocus()}.
822      */
captureStillPicture()823     private void captureStillPicture() {
824         try {
825             final Activity activity = getActivity();
826             if (null == activity || null == mCameraDevice) {
827                 return;
828             }
829             // This is the CaptureRequest.Builder that we use to take a picture.
830             final CaptureRequest.Builder captureBuilder =
831                     mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
832             captureBuilder.addTarget(mImageReader.getSurface());
833 
834             // Use the same AE and AF modes as the preview.
835             captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
836                     CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
837             setAutoFlash(captureBuilder);
838 
839             // Orientation
840             int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
841             captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
842 
843             CameraCaptureSession.CaptureCallback CaptureCallback
844                     = new CameraCaptureSession.CaptureCallback() {
845 
846                 @Override
847                 public void onCaptureCompleted(@NonNull CameraCaptureSession session,
848                                                @NonNull CaptureRequest request,
849                                                @NonNull TotalCaptureResult result) {
850                     Log.d(TAG, mFile.toString());
851                     unlockFocus();
852                 }
853             };
854 
855             mCaptureSession.stopRepeating();
856             mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
857         } catch (CameraAccessException e) {
858             e.printStackTrace();
859         }
860     }
861 
862     /**
863      * Retrieves the JPEG orientation from the specified screen rotation.
864      *
865      * @param rotation The screen rotation.
866      * @return The JPEG orientation (one of 0, 90, 270, and 360)
867      */
getOrientation(int rotation)868     private int getOrientation(int rotation) {
869         // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
870         // We have to take that into account and rotate JPEG properly.
871         // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS.
872         // For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
873         return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
874     }
875 
876     /**
877      * Unlock the focus. This method should be called when still image capture sequence is
878      * finished.
879      */
unlockFocus()880     private void unlockFocus() {
881         try {
882             // Reset the auto-focus trigger
883             mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
884                     CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
885             setAutoFlash(mPreviewRequestBuilder);
886             mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
887                     mBackgroundHandler);
888             // After this, the camera will go back to the normal state of preview.
889             mState = STATE_PREVIEW;
890             mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback,
891                     mBackgroundHandler);
892         } catch (CameraAccessException e) {
893             e.printStackTrace();
894         }
895     }
896 
897     @Override
onClick(View view)898     public void onClick(View view) {
899         switch (view.getId()) {
900             case R.id.picture: {
901                 takePicture();
902                 break;
903             }
904             case R.id.info: {
905                 Activity activity = getActivity();
906                 if (null != activity) {
907                     new AlertDialog.Builder(activity)
908                             .setMessage(R.string.intro_message)
909                             .setPositiveButton(android.R.string.ok, null)
910                             .show();
911                 }
912                 break;
913             }
914         }
915     }
916 
setAutoFlash(CaptureRequest.Builder requestBuilder)917     private void setAutoFlash(CaptureRequest.Builder requestBuilder) {
918         if (mFlashSupported) {
919             requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
920                     CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
921         }
922     }
923 
924     /**
925      * Saves a JPEG {@link Image} into the specified {@link File}.
926      */
927     private static class ImageSaver implements Runnable {
928 
929         /**
930          * The JPEG image
931          */
932         private final Image mImage;
933         /**
934          * The file we save the image into.
935          */
936         private final File mFile;
937 
ImageSaver(Image image, File file)938         public ImageSaver(Image image, File file) {
939             mImage = image;
940             mFile = file;
941         }
942 
943         @Override
run()944         public void run() {
945             ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
946             byte[] bytes = new byte[buffer.remaining()];
947             buffer.get(bytes);
948             FileOutputStream output = null;
949             try {
950                 output = new FileOutputStream(mFile);
951                 output.write(bytes);
952             } catch (IOException e) {
953                 e.printStackTrace();
954             } finally {
955                 mImage.close();
956                 if (null != output) {
957                     try {
958                         output.close();
959                     } catch (IOException e) {
960                         e.printStackTrace();
961                     }
962                 }
963             }
964         }
965 
966     }
967 
968     /**
969      * Compares two {@code Size}s based on their areas.
970      */
971     static class CompareSizesByArea implements Comparator<Size> {
972 
973         @Override
compare(Size lhs, Size rhs)974         public int compare(Size lhs, Size rhs) {
975             // We cast here to ensure the multiplications won't overflow
976             return Long.compare((long) lhs.getWidth() * lhs.getHeight(),
977                     (long) rhs.getWidth() * rhs.getHeight());
978         }
979 
980     }
981 
982     /**
983      * Shows an error message dialog.
984      */
985     public static class ErrorDialog extends DialogFragment {
986 
987         private static final String ARG_MESSAGE = "message";
988 
newInstance(String message)989         public static ErrorDialog newInstance(String message) {
990             ErrorDialog dialog = new ErrorDialog();
991             Bundle args = new Bundle();
992             args.putString(ARG_MESSAGE, message);
993             dialog.setArguments(args);
994             return dialog;
995         }
996 
997         @Override
onCreateDialog(Bundle savedInstanceState)998         public Dialog onCreateDialog(Bundle savedInstanceState) {
999             final Activity activity = getActivity();
1000             return new AlertDialog.Builder(activity)
1001                     .setMessage(getArguments().getString(ARG_MESSAGE))
1002                     .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1003                         @Override
1004                         public void onClick(DialogInterface dialogInterface, int i) {
1005                             if (activity != null && !activity.isFinishing()) {
1006                                 activity.finish();
1007                             }
1008                         }
1009                     })
1010                     .create();
1011         }
1012 
1013     }
1014 
1015     /**
1016      * Shows OK/Cancel confirmation dialog about camera permission.
1017      */
1018     public static class ConfirmationDialog extends DialogFragment {
1019 
1020         @Override
1021         public Dialog onCreateDialog(Bundle savedInstanceState) {
1022             final Fragment parent = getParentFragment();
1023             return new AlertDialog.Builder(getActivity())
1024                     .setMessage(R.string.request_permission)
1025                     .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1026                         @Override
1027                         public void onClick(DialogInterface dialog, int which) {
1028                             FragmentCompat.requestPermissions(parent,
1029                                     new String[]{Manifest.permission.CAMERA},
1030                                     REQUEST_CAMERA_PERMISSION);
1031                         }
1032                     })
1033                     .setNegativeButton(android.R.string.cancel,
1034                             new DialogInterface.OnClickListener() {
1035                                 @Override
1036                                 public void onClick(DialogInterface dialog, int which) {
1037                                     Activity activity = parent.getActivity();
1038                                     if (activity != null && !activity.isFinishing()) {
1039                                         activity.finish();
1040                                     }
1041                                 }
1042                             })
1043                     .create();
1044         }
1045     }
1046 
1047 }
1048