close
The Wayback Machine - https://web.archive.org/web/20171110005055/https://developer.android.com/samples/Camera2Basic/src/com.example.android.camera2basic/Camera2BasicFragment.html
Skip to content

Most visited

Recently visited

navigation
Camera2Basic / src / com.example.android.camera2basic /

Camera2BasicFragment.java

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

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Follow Google Developers on WeChat

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a short survey?
Help us improve the Android developer experience.
(Sep 2017 survey)