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 { 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 = "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 */ showToast(final String text)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 */ chooseOptimalSize(Size[] choices, int textureViewWidth, int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio)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 newInstance()417 public static Camera2BasicFragment newInstance() { 418 return new Camera2BasicFragment(); 419 } 420 421 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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 onViewCreated(final View view, Bundle savedInstanceState)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 onActivityCreated(Bundle savedInstanceState)435 public void onActivityCreated(Bundle savedInstanceState) { 436 super.onActivityCreated(savedInstanceState); 437 mFile = new File(getActivity().getExternalFilesDir(null), "pic.jpg"); 438 } 439 440 @Override onResume()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 onPause()457 public void onPause() { 458 closeCamera(); 459 stopBackgroundThread(); 460 super.onPause(); 461 } 462 requestCameraPermission()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 onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)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 */ setUpCameraOutputs(int width, int height)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 */ openCamera(int width, int height)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 */ closeCamera()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 */ startBackgroundThread()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 */ stopBackgroundThread()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 */ createCameraPreviewSession()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 */ configureTransform(int viewWidth, int viewHeight)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 */ takePicture()768 private void takePicture() { 769 lockFocus(); 770 } 771 772 /** 773 * Lock the focus as the first step for a still image capture. 774 */ lockFocus()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 */ runPrecaptureSequence()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 */ captureStillPicture()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 */ getOrientation(int rotation)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 */ unlockFocus()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 onClick(View view)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 setAutoFlash(CaptureRequest.Builder requestBuilder)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 ImageSaver(Image image, File file)927 public ImageSaver(Image image, File file) { 928 mImage = image; 929 mFile = file; 930 } 931 932 @Override run()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 compare(Size lhs, Size rhs)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 newInstance(String message)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 onCreateDialog(Bundle savedInstanceState)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 } 1035