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