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 }