1 /* 2 * Copyright 2015 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.camera2raw; 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.graphics.ImageFormat; 29 import android.graphics.Matrix; 30 import android.graphics.Point; 31 import android.graphics.RectF; 32 import android.graphics.SurfaceTexture; 33 import android.hardware.SensorManager; 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.CaptureFailure; 41 import android.hardware.camera2.CaptureRequest; 42 import android.hardware.camera2.CaptureResult; 43 import android.hardware.camera2.DngCreator; 44 import android.hardware.camera2.TotalCaptureResult; 45 import android.hardware.camera2.params.StreamConfigurationMap; 46 import android.media.Image; 47 import android.media.ImageReader; 48 import android.media.MediaScannerConnection; 49 import android.net.Uri; 50 import android.os.AsyncTask; 51 import android.os.Bundle; 52 import android.os.Environment; 53 import android.os.Handler; 54 import android.os.HandlerThread; 55 import android.os.Looper; 56 import android.os.Message; 57 import android.os.SystemClock; 58 import android.support.v13.app.FragmentCompat; 59 import android.support.v4.app.ActivityCompat; 60 import android.util.Log; 61 import android.util.Size; 62 import android.util.SparseIntArray; 63 import android.view.LayoutInflater; 64 import android.view.OrientationEventListener; 65 import android.view.Surface; 66 import android.view.TextureView; 67 import android.view.View; 68 import android.view.ViewGroup; 69 import android.widget.Toast; 70 71 import java.io.File; 72 import java.io.FileOutputStream; 73 import java.io.IOException; 74 import java.io.OutputStream; 75 import java.nio.ByteBuffer; 76 import java.text.SimpleDateFormat; 77 import java.util.ArrayList; 78 import java.util.Arrays; 79 import java.util.Collections; 80 import java.util.Comparator; 81 import java.util.Date; 82 import java.util.List; 83 import java.util.Locale; 84 import java.util.Map; 85 import java.util.TreeMap; 86 import java.util.concurrent.Semaphore; 87 import java.util.concurrent.TimeUnit; 88 import java.util.concurrent.atomic.AtomicInteger; 89 90 /** 91 * A fragment that demonstrates use of the Camera2 API to capture RAW and JPEG photos. 92 * <p/> 93 * In this example, the lifecycle of a single request to take a photo is: 94 * <ul> 95 * <li> 96 * The user presses the "Picture" button, resulting in a call to {@link #takePicture()}. 97 * </li> 98 * <li> 99 * {@link #takePicture()} initiates a pre-capture sequence that triggers the camera's built-in 100 * auto-focus, auto-exposure, and auto-white-balance algorithms (aka. "3A") to run. 101 * </li> 102 * <li> 103 * When the pre-capture sequence has finished, a {@link CaptureRequest} with a monotonically 104 * increasing request ID set by calls to {@link CaptureRequest.Builder#setTag(Object)} is sent to 105 * the camera to begin the JPEG and RAW capture sequence, and an 106 * {@link ImageSaver.ImageSaverBuilder} is stored for this request in the 107 * {@link #mJpegResultQueue} and {@link #mRawResultQueue}. 108 * </li> 109 * <li> 110 * As {@link CaptureResult}s and {@link Image}s become available via callbacks in a background 111 * thread, a {@link ImageSaver.ImageSaverBuilder} is looked up by the request ID in 112 * {@link #mJpegResultQueue} and {@link #mRawResultQueue} and updated. 113 * </li> 114 * <li> 115 * When all of the necessary results to save an image are available, the an {@link ImageSaver} is 116 * constructed by the {@link ImageSaver.ImageSaverBuilder} and passed to a separate background 117 * thread to save to a file. 118 * </li> 119 * </ul> 120 */ 121 public class Camera2RawFragment extends Fragment 122 implements View.OnClickListener, FragmentCompat.OnRequestPermissionsResultCallback { 123 124 /** 125 * Conversion from screen rotation to JPEG orientation. 126 */ 127 private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); 128 129 static { ORIENTATIONS.append(Surface.ROTATION_0, 0)130 ORIENTATIONS.append(Surface.ROTATION_0, 0); ORIENTATIONS.append(Surface.ROTATION_90, 90)131 ORIENTATIONS.append(Surface.ROTATION_90, 90); ORIENTATIONS.append(Surface.ROTATION_180, 180)132 ORIENTATIONS.append(Surface.ROTATION_180, 180); ORIENTATIONS.append(Surface.ROTATION_270, 270)133 ORIENTATIONS.append(Surface.ROTATION_270, 270); 134 } 135 136 /** 137 * Request code for camera permissions. 138 */ 139 private static final int REQUEST_CAMERA_PERMISSIONS = 1; 140 141 /** 142 * Permissions required to take a picture. 143 */ 144 private static final String[] CAMERA_PERMISSIONS = { 145 Manifest.permission.CAMERA, 146 Manifest.permission.READ_EXTERNAL_STORAGE, 147 Manifest.permission.WRITE_EXTERNAL_STORAGE, 148 }; 149 150 /** 151 * Timeout for the pre-capture sequence. 152 */ 153 private static final long PRECAPTURE_TIMEOUT_MS = 1000; 154 155 /** 156 * Tolerance when comparing aspect ratios. 157 */ 158 private static final double ASPECT_RATIO_TOLERANCE = 0.005; 159 160 /** 161 * Max preview width that is guaranteed by Camera2 API 162 */ 163 private static final int MAX_PREVIEW_WIDTH = 1920; 164 165 /** 166 * Max preview height that is guaranteed by Camera2 API 167 */ 168 private static final int MAX_PREVIEW_HEIGHT = 1080; 169 170 /** 171 * Tag for the {@link Log}. 172 */ 173 private static final String TAG = "Camera2RawFragment"; 174 175 /** 176 * Camera state: Device is closed. 177 */ 178 private static final int STATE_CLOSED = 0; 179 180 /** 181 * Camera state: Device is opened, but is not capturing. 182 */ 183 private static final int STATE_OPENED = 1; 184 185 /** 186 * Camera state: Showing camera preview. 187 */ 188 private static final int STATE_PREVIEW = 2; 189 190 /** 191 * Camera state: Waiting for 3A convergence before capturing a photo. 192 */ 193 private static final int STATE_WAITING_FOR_3A_CONVERGENCE = 3; 194 195 /** 196 * An {@link OrientationEventListener} used to determine when device rotation has occurred. 197 * This is mainly necessary for when the device is rotated by 180 degrees, in which case 198 * onCreate or onConfigurationChanged is not called as the view dimensions remain the same, 199 * but the orientation of the has changed, and thus the preview rotation must be updated. 200 */ 201 private OrientationEventListener mOrientationListener; 202 203 /** 204 * {@link TextureView.SurfaceTextureListener} handles several lifecycle events of a 205 * {@link TextureView}. 206 */ 207 private final TextureView.SurfaceTextureListener mSurfaceTextureListener 208 = new TextureView.SurfaceTextureListener() { 209 210 @Override 211 public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { 212 configureTransform(width, height); 213 } 214 215 @Override 216 public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { 217 configureTransform(width, height); 218 } 219 220 @Override 221 public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { 222 synchronized (mCameraStateLock) { 223 mPreviewSize = null; 224 } 225 return true; 226 } 227 228 @Override 229 public void onSurfaceTextureUpdated(SurfaceTexture texture) { 230 } 231 232 }; 233 234 /** 235 * An {@link AutoFitTextureView} for camera preview. 236 */ 237 private AutoFitTextureView mTextureView; 238 239 /** 240 * An additional thread for running tasks that shouldn't block the UI. This is used for all 241 * callbacks from the {@link CameraDevice} and {@link CameraCaptureSession}s. 242 */ 243 private HandlerThread mBackgroundThread; 244 245 /** 246 * A counter for tracking corresponding {@link CaptureRequest}s and {@link CaptureResult}s 247 * across the {@link CameraCaptureSession} capture callbacks. 248 */ 249 private final AtomicInteger mRequestCounter = new AtomicInteger(); 250 251 /** 252 * A {@link Semaphore} to prevent the app from exiting before closing the camera. 253 */ 254 private final Semaphore mCameraOpenCloseLock = new Semaphore(1); 255 256 /** 257 * A lock protecting camera state. 258 */ 259 private final Object mCameraStateLock = new Object(); 260 261 // ********************************************************************************************* 262 // State protected by mCameraStateLock. 263 // 264 // The following state is used across both the UI and background threads. Methods with "Locked" 265 // in the name expect mCameraStateLock to be held while calling. 266 267 /** 268 * ID of the current {@link CameraDevice}. 269 */ 270 private String mCameraId; 271 272 /** 273 * A {@link CameraCaptureSession } for camera preview. 274 */ 275 private CameraCaptureSession mCaptureSession; 276 277 /** 278 * A reference to the open {@link CameraDevice}. 279 */ 280 private CameraDevice mCameraDevice; 281 282 /** 283 * The {@link Size} of camera preview. 284 */ 285 private Size mPreviewSize; 286 287 /** 288 * The {@link CameraCharacteristics} for the currently configured camera device. 289 */ 290 private CameraCharacteristics mCharacteristics; 291 292 /** 293 * A {@link Handler} for running tasks in the background. 294 */ 295 private Handler mBackgroundHandler; 296 297 /** 298 * A reference counted holder wrapping the {@link ImageReader} that handles JPEG image 299 * captures. This is used to allow us to clean up the {@link ImageReader} when all background 300 * tasks using its {@link Image}s have completed. 301 */ 302 private RefCountedAutoCloseable<ImageReader> mJpegImageReader; 303 304 /** 305 * A reference counted holder wrapping the {@link ImageReader} that handles RAW image captures. 306 * This is used to allow us to clean up the {@link ImageReader} when all background tasks using 307 * its {@link Image}s have completed. 308 */ 309 private RefCountedAutoCloseable<ImageReader> mRawImageReader; 310 311 /** 312 * Whether or not the currently configured camera device is fixed-focus. 313 */ 314 private boolean mNoAFRun = false; 315 316 /** 317 * Number of pending user requests to capture a photo. 318 */ 319 private int mPendingUserCaptures = 0; 320 321 /** 322 * Request ID to {@link ImageSaver.ImageSaverBuilder} mapping for in-progress JPEG captures. 323 */ 324 private final TreeMap<Integer, ImageSaver.ImageSaverBuilder> mJpegResultQueue = new TreeMap<>(); 325 326 /** 327 * Request ID to {@link ImageSaver.ImageSaverBuilder} mapping for in-progress RAW captures. 328 */ 329 private final TreeMap<Integer, ImageSaver.ImageSaverBuilder> mRawResultQueue = new TreeMap<>(); 330 331 /** 332 * {@link CaptureRequest.Builder} for the camera preview 333 */ 334 private CaptureRequest.Builder mPreviewRequestBuilder; 335 336 /** 337 * The state of the camera device. 338 * 339 * @see #mPreCaptureCallback 340 */ 341 private int mState = STATE_CLOSED; 342 343 /** 344 * Timer to use with pre-capture sequence to ensure a timely capture if 3A convergence is 345 * taking too long. 346 */ 347 private long mCaptureTimer; 348 349 //********************************************************************************************** 350 351 /** 352 * {@link CameraDevice.StateCallback} is called when the currently active {@link CameraDevice} 353 * changes its state. 354 */ 355 private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { 356 357 @Override 358 public void onOpened(CameraDevice cameraDevice) { 359 // This method is called when the camera is opened. We start camera preview here if 360 // the TextureView displaying this has been set up. 361 synchronized (mCameraStateLock) { 362 mState = STATE_OPENED; 363 mCameraOpenCloseLock.release(); 364 mCameraDevice = cameraDevice; 365 366 // Start the preview session if the TextureView has been set up already. 367 if (mPreviewSize != null && mTextureView.isAvailable()) { 368 createCameraPreviewSessionLocked(); 369 } 370 } 371 } 372 373 @Override 374 public void onDisconnected(CameraDevice cameraDevice) { 375 synchronized (mCameraStateLock) { 376 mState = STATE_CLOSED; 377 mCameraOpenCloseLock.release(); 378 cameraDevice.close(); 379 mCameraDevice = null; 380 } 381 } 382 383 @Override 384 public void onError(CameraDevice cameraDevice, int error) { 385 Log.e(TAG, "Received camera device error: " + error); 386 synchronized (mCameraStateLock) { 387 mState = STATE_CLOSED; 388 mCameraOpenCloseLock.release(); 389 cameraDevice.close(); 390 mCameraDevice = null; 391 } 392 Activity activity = getActivity(); 393 if (null != activity) { 394 activity.finish(); 395 } 396 } 397 398 }; 399 400 /** 401 * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a 402 * JPEG image is ready to be saved. 403 */ 404 private final ImageReader.OnImageAvailableListener mOnJpegImageAvailableListener 405 = new ImageReader.OnImageAvailableListener() { 406 407 @Override 408 public void onImageAvailable(ImageReader reader) { 409 dequeueAndSaveImage(mJpegResultQueue, mJpegImageReader); 410 } 411 412 }; 413 414 /** 415 * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a 416 * RAW image is ready to be saved. 417 */ 418 private final ImageReader.OnImageAvailableListener mOnRawImageAvailableListener 419 = new ImageReader.OnImageAvailableListener() { 420 421 @Override 422 public void onImageAvailable(ImageReader reader) { 423 dequeueAndSaveImage(mRawResultQueue, mRawImageReader); 424 } 425 426 }; 427 428 /** 429 * A {@link CameraCaptureSession.CaptureCallback} that handles events for the preview and 430 * pre-capture sequence. 431 */ 432 private CameraCaptureSession.CaptureCallback mPreCaptureCallback 433 = new CameraCaptureSession.CaptureCallback() { 434 435 private void process(CaptureResult result) { 436 synchronized (mCameraStateLock) { 437 switch (mState) { 438 case STATE_PREVIEW: { 439 // We have nothing to do when the camera preview is running normally. 440 break; 441 } 442 case STATE_WAITING_FOR_3A_CONVERGENCE: { 443 boolean readyToCapture = true; 444 if (!mNoAFRun) { 445 Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); 446 if (afState == null) { 447 break; 448 } 449 450 // If auto-focus has reached locked state, we are ready to capture 451 readyToCapture = 452 (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || 453 afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED); 454 } 455 456 // If we are running on an non-legacy device, we should also wait until 457 // auto-exposure and auto-white-balance have converged as well before 458 // taking a picture. 459 if (!isLegacyLocked()) { 460 Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 461 Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE); 462 if (aeState == null || awbState == null) { 463 break; 464 } 465 466 readyToCapture = readyToCapture && 467 aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED && 468 awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED; 469 } 470 471 // If we haven't finished the pre-capture sequence but have hit our maximum 472 // wait timeout, too bad! Begin capture anyway. 473 if (!readyToCapture && hitTimeoutLocked()) { 474 Log.w(TAG, "Timed out waiting for pre-capture sequence to complete."); 475 readyToCapture = true; 476 } 477 478 if (readyToCapture && mPendingUserCaptures > 0) { 479 // Capture once for each user tap of the "Picture" button. 480 while (mPendingUserCaptures > 0) { 481 captureStillPictureLocked(); 482 mPendingUserCaptures--; 483 } 484 // After this, the camera will go back to the normal state of preview. 485 mState = STATE_PREVIEW; 486 } 487 } 488 } 489 } 490 } 491 492 @Override 493 public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, 494 CaptureResult partialResult) { 495 process(partialResult); 496 } 497 498 @Override 499 public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, 500 TotalCaptureResult result) { 501 process(result); 502 } 503 504 }; 505 506 /** 507 * A {@link CameraCaptureSession.CaptureCallback} that handles the still JPEG and RAW capture 508 * request. 509 */ 510 private final CameraCaptureSession.CaptureCallback mCaptureCallback 511 = new CameraCaptureSession.CaptureCallback() { 512 @Override 513 public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, 514 long timestamp, long frameNumber) { 515 String currentDateTime = generateTimestamp(); 516 File rawFile = new File(Environment. 517 getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), 518 "RAW_" + currentDateTime + ".dng"); 519 File jpegFile = new File(Environment. 520 getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), 521 "JPEG_" + currentDateTime + ".jpg"); 522 523 // Look up the ImageSaverBuilder for this request and update it with the file name 524 // based on the capture start time. 525 ImageSaver.ImageSaverBuilder jpegBuilder; 526 ImageSaver.ImageSaverBuilder rawBuilder; 527 int requestId = (int) request.getTag(); 528 synchronized (mCameraStateLock) { 529 jpegBuilder = mJpegResultQueue.get(requestId); 530 rawBuilder = mRawResultQueue.get(requestId); 531 } 532 533 if (jpegBuilder != null) jpegBuilder.setFile(jpegFile); 534 if (rawBuilder != null) rawBuilder.setFile(rawFile); 535 } 536 537 @Override 538 public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, 539 TotalCaptureResult result) { 540 int requestId = (int) request.getTag(); 541 ImageSaver.ImageSaverBuilder jpegBuilder; 542 ImageSaver.ImageSaverBuilder rawBuilder; 543 StringBuilder sb = new StringBuilder(); 544 545 // Look up the ImageSaverBuilder for this request and update it with the CaptureResult 546 synchronized (mCameraStateLock) { 547 jpegBuilder = mJpegResultQueue.get(requestId); 548 rawBuilder = mRawResultQueue.get(requestId); 549 550 // If we have all the results necessary, save the image to a file in the background. 551 handleCompletionLocked(requestId, jpegBuilder, mJpegResultQueue); 552 handleCompletionLocked(requestId, rawBuilder, mRawResultQueue); 553 554 if (jpegBuilder != null) { 555 jpegBuilder.setResult(result); 556 sb.append("Saving JPEG as: "); 557 sb.append(jpegBuilder.getSaveLocation()); 558 } 559 if (rawBuilder != null) { 560 rawBuilder.setResult(result); 561 if (jpegBuilder != null) sb.append(", "); 562 sb.append("Saving RAW as: "); 563 sb.append(rawBuilder.getSaveLocation()); 564 } 565 finishedCaptureLocked(); 566 } 567 568 showToast(sb.toString()); 569 } 570 571 @Override 572 public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, 573 CaptureFailure failure) { 574 int requestId = (int) request.getTag(); 575 synchronized (mCameraStateLock) { 576 mJpegResultQueue.remove(requestId); 577 mRawResultQueue.remove(requestId); 578 finishedCaptureLocked(); 579 } 580 showToast("Capture failed!"); 581 } 582 583 }; 584 585 /** 586 * A {@link Handler} for showing {@link Toast}s on the UI thread. 587 */ 588 private final Handler mMessageHandler = new Handler(Looper.getMainLooper()) { 589 @Override 590 public void handleMessage(Message msg) { 591 Activity activity = getActivity(); 592 if (activity != null) { 593 Toast.makeText(activity, (String) msg.obj, Toast.LENGTH_SHORT).show(); 594 } 595 } 596 }; 597 newInstance()598 public static Camera2RawFragment newInstance() { 599 return new Camera2RawFragment(); 600 } 601 602 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)603 public View onCreateView(LayoutInflater inflater, ViewGroup container, 604 Bundle savedInstanceState) { 605 return inflater.inflate(R.layout.fragment_camera2_basic, container, false); 606 } 607 608 @Override onViewCreated(final View view, Bundle savedInstanceState)609 public void onViewCreated(final View view, Bundle savedInstanceState) { 610 view.findViewById(R.id.picture).setOnClickListener(this); 611 view.findViewById(R.id.info).setOnClickListener(this); 612 mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture); 613 614 // Setup a new OrientationEventListener. This is used to handle rotation events like a 615 // 180 degree rotation that do not normally trigger a call to onCreate to do view re-layout 616 // or otherwise cause the preview TextureView's size to change. 617 mOrientationListener = new OrientationEventListener(getActivity(), 618 SensorManager.SENSOR_DELAY_NORMAL) { 619 @Override 620 public void onOrientationChanged(int orientation) { 621 if (mTextureView != null && mTextureView.isAvailable()) { 622 configureTransform(mTextureView.getWidth(), mTextureView.getHeight()); 623 } 624 } 625 }; 626 } 627 628 @Override onResume()629 public void onResume() { 630 super.onResume(); 631 startBackgroundThread(); 632 openCamera(); 633 634 // When the screen is turned off and turned back on, the SurfaceTexture is already 635 // available, and "onSurfaceTextureAvailable" will not be called. In that case, we should 636 // configure the preview bounds here (otherwise, we wait until the surface is ready in 637 // the SurfaceTextureListener). 638 if (mTextureView.isAvailable()) { 639 configureTransform(mTextureView.getWidth(), mTextureView.getHeight()); 640 } else { 641 mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); 642 } 643 if (mOrientationListener != null && mOrientationListener.canDetectOrientation()) { 644 mOrientationListener.enable(); 645 } 646 } 647 648 @Override onPause()649 public void onPause() { 650 if (mOrientationListener != null) { 651 mOrientationListener.disable(); 652 } 653 closeCamera(); 654 stopBackgroundThread(); 655 super.onPause(); 656 } 657 658 @Override onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)659 public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 660 if (requestCode == REQUEST_CAMERA_PERMISSIONS) { 661 for (int result : grantResults) { 662 if (result != PackageManager.PERMISSION_GRANTED) { 663 showMissingPermissionError(); 664 return; 665 } 666 } 667 } else { 668 super.onRequestPermissionsResult(requestCode, permissions, grantResults); 669 } 670 } 671 672 @Override onClick(View view)673 public void onClick(View view) { 674 switch (view.getId()) { 675 case R.id.picture: { 676 takePicture(); 677 break; 678 } 679 case R.id.info: { 680 Activity activity = getActivity(); 681 if (null != activity) { 682 new AlertDialog.Builder(activity) 683 .setMessage(R.string.intro_message) 684 .setPositiveButton(android.R.string.ok, null) 685 .show(); 686 } 687 break; 688 } 689 } 690 } 691 692 /** 693 * Sets up state related to camera that is needed before opening a {@link CameraDevice}. 694 */ setUpCameraOutputs()695 private boolean setUpCameraOutputs() { 696 Activity activity = getActivity(); 697 CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); 698 if (manager == null) { 699 ErrorDialog.buildErrorDialog("This device doesn't support Camera2 API."). 700 show(getFragmentManager(), "dialog"); 701 return false; 702 } 703 try { 704 // Find a CameraDevice that supports RAW captures, and configure state. 705 for (String cameraId : manager.getCameraIdList()) { 706 CameraCharacteristics characteristics 707 = manager.getCameraCharacteristics(cameraId); 708 709 // We only use a camera that supports RAW in this sample. 710 if (!contains(characteristics.get( 711 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES), 712 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) { 713 continue; 714 } 715 716 StreamConfigurationMap map = characteristics.get( 717 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 718 719 // For still image captures, we use the largest available size. 720 Size largestJpeg = Collections.max( 721 Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), 722 new CompareSizesByArea()); 723 724 Size largestRaw = Collections.max( 725 Arrays.asList(map.getOutputSizes(ImageFormat.RAW_SENSOR)), 726 new CompareSizesByArea()); 727 728 synchronized (mCameraStateLock) { 729 // Set up ImageReaders for JPEG and RAW outputs. Place these in a reference 730 // counted wrapper to ensure they are only closed when all background tasks 731 // using them are finished. 732 if (mJpegImageReader == null || mJpegImageReader.getAndRetain() == null) { 733 mJpegImageReader = new RefCountedAutoCloseable<>( 734 ImageReader.newInstance(largestJpeg.getWidth(), 735 largestJpeg.getHeight(), ImageFormat.JPEG, /*maxImages*/5)); 736 } 737 mJpegImageReader.get().setOnImageAvailableListener( 738 mOnJpegImageAvailableListener, mBackgroundHandler); 739 740 if (mRawImageReader == null || mRawImageReader.getAndRetain() == null) { 741 mRawImageReader = new RefCountedAutoCloseable<>( 742 ImageReader.newInstance(largestRaw.getWidth(), 743 largestRaw.getHeight(), ImageFormat.RAW_SENSOR, /*maxImages*/ 5)); 744 } 745 mRawImageReader.get().setOnImageAvailableListener( 746 mOnRawImageAvailableListener, mBackgroundHandler); 747 748 mCharacteristics = characteristics; 749 mCameraId = cameraId; 750 } 751 return true; 752 } 753 } catch (CameraAccessException e) { 754 e.printStackTrace(); 755 } 756 757 // If we found no suitable cameras for capturing RAW, warn the user. 758 ErrorDialog.buildErrorDialog("This device doesn't support capturing RAW photos"). 759 show(getFragmentManager(), "dialog"); 760 return false; 761 } 762 763 /** 764 * Opens the camera specified by {@link #mCameraId}. 765 */ openCamera()766 private void openCamera() { 767 if (!setUpCameraOutputs()) { 768 return; 769 } 770 if (!hasAllPermissionsGranted()) { 771 requestCameraPermissions(); 772 return; 773 } 774 775 Activity activity = getActivity(); 776 CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); 777 try { 778 // Wait for any previously running session to finish. 779 if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { 780 throw new RuntimeException("Time out waiting to lock camera opening."); 781 } 782 783 String cameraId; 784 Handler backgroundHandler; 785 synchronized (mCameraStateLock) { 786 cameraId = mCameraId; 787 backgroundHandler = mBackgroundHandler; 788 } 789 790 // Attempt to open the camera. mStateCallback will be called on the background handler's 791 // thread when this succeeds or fails. 792 manager.openCamera(cameraId, mStateCallback, backgroundHandler); 793 } catch (CameraAccessException e) { 794 e.printStackTrace(); 795 } catch (InterruptedException e) { 796 throw new RuntimeException("Interrupted while trying to lock camera opening.", e); 797 } 798 } 799 800 /** 801 * Requests permissions necessary to use camera and save pictures. 802 */ requestCameraPermissions()803 private void requestCameraPermissions() { 804 if (shouldShowRationale()) { 805 PermissionConfirmationDialog.newInstance().show(getChildFragmentManager(), "dialog"); 806 } else { 807 FragmentCompat.requestPermissions(this, CAMERA_PERMISSIONS, REQUEST_CAMERA_PERMISSIONS); 808 } 809 } 810 811 /** 812 * Tells whether all the necessary permissions are granted to this app. 813 * 814 * @return True if all the required permissions are granted. 815 */ hasAllPermissionsGranted()816 private boolean hasAllPermissionsGranted() { 817 for (String permission : CAMERA_PERMISSIONS) { 818 if (ActivityCompat.checkSelfPermission(getActivity(), permission) 819 != PackageManager.PERMISSION_GRANTED) { 820 return false; 821 } 822 } 823 return true; 824 } 825 826 /** 827 * Gets whether you should show UI with rationale for requesting the permissions. 828 * 829 * @return True if the UI should be shown. 830 */ shouldShowRationale()831 private boolean shouldShowRationale() { 832 for (String permission : CAMERA_PERMISSIONS) { 833 if (FragmentCompat.shouldShowRequestPermissionRationale(this, permission)) { 834 return true; 835 } 836 } 837 return false; 838 } 839 840 /** 841 * Shows that this app really needs the permission and finishes the app. 842 */ showMissingPermissionError()843 private void showMissingPermissionError() { 844 Activity activity = getActivity(); 845 if (activity != null) { 846 Toast.makeText(activity, R.string.request_permission, Toast.LENGTH_SHORT).show(); 847 activity.finish(); 848 } 849 } 850 851 /** 852 * Closes the current {@link CameraDevice}. 853 */ closeCamera()854 private void closeCamera() { 855 try { 856 mCameraOpenCloseLock.acquire(); 857 synchronized (mCameraStateLock) { 858 859 // Reset state and clean up resources used by the camera. 860 // Note: After calling this, the ImageReaders will be closed after any background 861 // tasks saving Images from these readers have been completed. 862 mPendingUserCaptures = 0; 863 mState = STATE_CLOSED; 864 if (null != mCaptureSession) { 865 mCaptureSession.close(); 866 mCaptureSession = null; 867 } 868 if (null != mCameraDevice) { 869 mCameraDevice.close(); 870 mCameraDevice = null; 871 } 872 if (null != mJpegImageReader) { 873 mJpegImageReader.close(); 874 mJpegImageReader = null; 875 } 876 if (null != mRawImageReader) { 877 mRawImageReader.close(); 878 mRawImageReader = null; 879 } 880 } 881 } catch (InterruptedException e) { 882 throw new RuntimeException("Interrupted while trying to lock camera closing.", e); 883 } finally { 884 mCameraOpenCloseLock.release(); 885 } 886 } 887 888 /** 889 * Starts a background thread and its {@link Handler}. 890 */ startBackgroundThread()891 private void startBackgroundThread() { 892 mBackgroundThread = new HandlerThread("CameraBackground"); 893 mBackgroundThread.start(); 894 synchronized (mCameraStateLock) { 895 mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); 896 } 897 } 898 899 /** 900 * Stops the background thread and its {@link Handler}. 901 */ stopBackgroundThread()902 private void stopBackgroundThread() { 903 mBackgroundThread.quitSafely(); 904 try { 905 mBackgroundThread.join(); 906 mBackgroundThread = null; 907 synchronized (mCameraStateLock) { 908 mBackgroundHandler = null; 909 } 910 } catch (InterruptedException e) { 911 e.printStackTrace(); 912 } 913 } 914 915 /** 916 * Creates a new {@link CameraCaptureSession} for camera preview. 917 * <p/> 918 * Call this only with {@link #mCameraStateLock} held. 919 */ createCameraPreviewSessionLocked()920 private void createCameraPreviewSessionLocked() { 921 try { 922 SurfaceTexture texture = mTextureView.getSurfaceTexture(); 923 // We configure the size of default buffer to be the size of camera preview we want. 924 texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); 925 926 // This is the output Surface we need to start preview. 927 Surface surface = new Surface(texture); 928 929 // We set up a CaptureRequest.Builder with the output Surface. 930 mPreviewRequestBuilder 931 = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 932 mPreviewRequestBuilder.addTarget(surface); 933 934 // Here, we create a CameraCaptureSession for camera preview. 935 mCameraDevice.createCaptureSession(Arrays.asList(surface, 936 mJpegImageReader.get().getSurface(), 937 mRawImageReader.get().getSurface()), new CameraCaptureSession.StateCallback() { 938 @Override 939 public void onConfigured(CameraCaptureSession cameraCaptureSession) { 940 synchronized (mCameraStateLock) { 941 // The camera is already closed 942 if (null == mCameraDevice) { 943 return; 944 } 945 946 try { 947 setup3AControlsLocked(mPreviewRequestBuilder); 948 // Finally, we start displaying the camera preview. 949 cameraCaptureSession.setRepeatingRequest( 950 mPreviewRequestBuilder.build(), 951 mPreCaptureCallback, mBackgroundHandler); 952 mState = STATE_PREVIEW; 953 } catch (CameraAccessException | IllegalStateException e) { 954 e.printStackTrace(); 955 return; 956 } 957 // When the session is ready, we start displaying the preview. 958 mCaptureSession = cameraCaptureSession; 959 } 960 } 961 962 @Override 963 public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { 964 showToast("Failed to configure camera."); 965 } 966 }, mBackgroundHandler 967 ); 968 } catch (CameraAccessException e) { 969 e.printStackTrace(); 970 } 971 } 972 973 /** 974 * Configure the given {@link CaptureRequest.Builder} to use auto-focus, auto-exposure, and 975 * auto-white-balance controls if available. 976 * <p/> 977 * Call this only with {@link #mCameraStateLock} held. 978 * 979 * @param builder the builder to configure. 980 */ setup3AControlsLocked(CaptureRequest.Builder builder)981 private void setup3AControlsLocked(CaptureRequest.Builder builder) { 982 // Enable auto-magical 3A run by camera device 983 builder.set(CaptureRequest.CONTROL_MODE, 984 CaptureRequest.CONTROL_MODE_AUTO); 985 986 Float minFocusDist = 987 mCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE); 988 989 // If MINIMUM_FOCUS_DISTANCE is 0, lens is fixed-focus and we need to skip the AF run. 990 mNoAFRun = (minFocusDist == null || minFocusDist == 0); 991 992 if (!mNoAFRun) { 993 // If there is a "continuous picture" mode available, use it, otherwise default to AUTO. 994 if (contains(mCharacteristics.get( 995 CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES), 996 CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)) { 997 builder.set(CaptureRequest.CONTROL_AF_MODE, 998 CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 999 } else { 1000 builder.set(CaptureRequest.CONTROL_AF_MODE, 1001 CaptureRequest.CONTROL_AF_MODE_AUTO); 1002 } 1003 } 1004 1005 // If there is an auto-magical flash control mode available, use it, otherwise default to 1006 // the "on" mode, which is guaranteed to always be available. 1007 if (contains(mCharacteristics.get( 1008 CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES), 1009 CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)) { 1010 builder.set(CaptureRequest.CONTROL_AE_MODE, 1011 CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); 1012 } else { 1013 builder.set(CaptureRequest.CONTROL_AE_MODE, 1014 CaptureRequest.CONTROL_AE_MODE_ON); 1015 } 1016 1017 // If there is an auto-magical white balance control mode available, use it. 1018 if (contains(mCharacteristics.get( 1019 CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES), 1020 CaptureRequest.CONTROL_AWB_MODE_AUTO)) { 1021 // Allow AWB to run auto-magically if this device supports this 1022 builder.set(CaptureRequest.CONTROL_AWB_MODE, 1023 CaptureRequest.CONTROL_AWB_MODE_AUTO); 1024 } 1025 } 1026 1027 /** 1028 * Configure the necessary {@link android.graphics.Matrix} transformation to `mTextureView`, 1029 * and start/restart the preview capture session if necessary. 1030 * <p/> 1031 * This method should be called after the camera state has been initialized in 1032 * setUpCameraOutputs. 1033 * 1034 * @param viewWidth The width of `mTextureView` 1035 * @param viewHeight The height of `mTextureView` 1036 */ configureTransform(int viewWidth, int viewHeight)1037 private void configureTransform(int viewWidth, int viewHeight) { 1038 Activity activity = getActivity(); 1039 synchronized (mCameraStateLock) { 1040 if (null == mTextureView || null == activity) { 1041 return; 1042 } 1043 1044 StreamConfigurationMap map = mCharacteristics.get( 1045 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 1046 1047 // For still image captures, we always use the largest available size. 1048 Size largestJpeg = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), 1049 new CompareSizesByArea()); 1050 1051 // Find the rotation of the device relative to the native device orientation. 1052 int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 1053 Point displaySize = new Point(); 1054 activity.getWindowManager().getDefaultDisplay().getSize(displaySize); 1055 1056 // Find the rotation of the device relative to the camera sensor's orientation. 1057 int totalRotation = sensorToDeviceRotation(mCharacteristics, deviceRotation); 1058 1059 // Swap the view dimensions for calculation as needed if they are rotated relative to 1060 // the sensor. 1061 boolean swappedDimensions = totalRotation == 90 || totalRotation == 270; 1062 int rotatedViewWidth = viewWidth; 1063 int rotatedViewHeight = viewHeight; 1064 int maxPreviewWidth = displaySize.x; 1065 int maxPreviewHeight = displaySize.y; 1066 1067 if (swappedDimensions) { 1068 rotatedViewWidth = viewHeight; 1069 rotatedViewHeight = viewWidth; 1070 maxPreviewWidth = displaySize.y; 1071 maxPreviewHeight = displaySize.x; 1072 } 1073 1074 // Preview should not be larger than display size and 1080p. 1075 if (maxPreviewWidth > MAX_PREVIEW_WIDTH) { 1076 maxPreviewWidth = MAX_PREVIEW_WIDTH; 1077 } 1078 1079 if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) { 1080 maxPreviewHeight = MAX_PREVIEW_HEIGHT; 1081 } 1082 1083 // Find the best preview size for these view dimensions and configured JPEG size. 1084 Size previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), 1085 rotatedViewWidth, rotatedViewHeight, maxPreviewWidth, maxPreviewHeight, 1086 largestJpeg); 1087 1088 if (swappedDimensions) { 1089 mTextureView.setAspectRatio( 1090 previewSize.getHeight(), previewSize.getWidth()); 1091 } else { 1092 mTextureView.setAspectRatio( 1093 previewSize.getWidth(), previewSize.getHeight()); 1094 } 1095 1096 // Find rotation of device in degrees (reverse device orientation for front-facing 1097 // cameras). 1098 int rotation = (mCharacteristics.get(CameraCharacteristics.LENS_FACING) == 1099 CameraCharacteristics.LENS_FACING_FRONT) ? 1100 (360 + ORIENTATIONS.get(deviceRotation)) % 360 : 1101 (360 - ORIENTATIONS.get(deviceRotation)) % 360; 1102 1103 Matrix matrix = new Matrix(); 1104 RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); 1105 RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); 1106 float centerX = viewRect.centerX(); 1107 float centerY = viewRect.centerY(); 1108 1109 // Initially, output stream images from the Camera2 API will be rotated to the native 1110 // device orientation from the sensor's orientation, and the TextureView will default to 1111 // scaling these buffers to fill it's view bounds. If the aspect ratios and relative 1112 // orientations are correct, this is fine. 1113 // 1114 // However, if the device orientation has been rotated relative to its native 1115 // orientation so that the TextureView's dimensions are swapped relative to the 1116 // native device orientation, we must do the following to ensure the output stream 1117 // images are not incorrectly scaled by the TextureView: 1118 // - Undo the scale-to-fill from the output buffer's dimensions (i.e. its dimensions 1119 // in the native device orientation) to the TextureView's dimension. 1120 // - Apply a scale-to-fill from the output buffer's rotated dimensions 1121 // (i.e. its dimensions in the current device orientation) to the TextureView's 1122 // dimensions. 1123 // - Apply the rotation from the native device orientation to the current device 1124 // rotation. 1125 if (Surface.ROTATION_90 == deviceRotation || Surface.ROTATION_270 == deviceRotation) { 1126 bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); 1127 matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); 1128 float scale = Math.max( 1129 (float) viewHeight / previewSize.getHeight(), 1130 (float) viewWidth / previewSize.getWidth()); 1131 matrix.postScale(scale, scale, centerX, centerY); 1132 1133 } 1134 matrix.postRotate(rotation, centerX, centerY); 1135 1136 mTextureView.setTransform(matrix); 1137 1138 // Start or restart the active capture session if the preview was initialized or 1139 // if its aspect ratio changed significantly. 1140 if (mPreviewSize == null || !checkAspectsEqual(previewSize, mPreviewSize)) { 1141 mPreviewSize = previewSize; 1142 if (mState != STATE_CLOSED) { 1143 createCameraPreviewSessionLocked(); 1144 } 1145 } 1146 } 1147 } 1148 1149 /** 1150 * Initiate a still image capture. 1151 * <p/> 1152 * This function sends a capture request that initiates a pre-capture sequence in our state 1153 * machine that waits for auto-focus to finish, ending in a "locked" state where the lens is no 1154 * longer moving, waits for auto-exposure to choose a good exposure value, and waits for 1155 * auto-white-balance to converge. 1156 */ takePicture()1157 private void takePicture() { 1158 synchronized (mCameraStateLock) { 1159 mPendingUserCaptures++; 1160 1161 // If we already triggered a pre-capture sequence, or are in a state where we cannot 1162 // do this, return immediately. 1163 if (mState != STATE_PREVIEW) { 1164 return; 1165 } 1166 1167 try { 1168 // Trigger an auto-focus run if camera is capable. If the camera is already focused, 1169 // this should do nothing. 1170 if (!mNoAFRun) { 1171 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 1172 CameraMetadata.CONTROL_AF_TRIGGER_START); 1173 } 1174 1175 // If this is not a legacy device, we can also trigger an auto-exposure metering 1176 // run. 1177 if (!isLegacyLocked()) { 1178 // Tell the camera to lock focus. 1179 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, 1180 CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START); 1181 } 1182 1183 // Update state machine to wait for auto-focus, auto-exposure, and 1184 // auto-white-balance (aka. "3A") to converge. 1185 mState = STATE_WAITING_FOR_3A_CONVERGENCE; 1186 1187 // Start a timer for the pre-capture sequence. 1188 startTimerLocked(); 1189 1190 // Replace the existing repeating request with one with updated 3A triggers. 1191 mCaptureSession.capture(mPreviewRequestBuilder.build(), mPreCaptureCallback, 1192 mBackgroundHandler); 1193 } catch (CameraAccessException e) { 1194 e.printStackTrace(); 1195 } 1196 } 1197 } 1198 1199 /** 1200 * Send a capture request to the camera device that initiates a capture targeting the JPEG and 1201 * RAW outputs. 1202 * <p/> 1203 * Call this only with {@link #mCameraStateLock} held. 1204 */ captureStillPictureLocked()1205 private void captureStillPictureLocked() { 1206 try { 1207 final Activity activity = getActivity(); 1208 if (null == activity || null == mCameraDevice) { 1209 return; 1210 } 1211 // This is the CaptureRequest.Builder that we use to take a picture. 1212 final CaptureRequest.Builder captureBuilder = 1213 mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); 1214 1215 captureBuilder.addTarget(mJpegImageReader.get().getSurface()); 1216 captureBuilder.addTarget(mRawImageReader.get().getSurface()); 1217 1218 // Use the same AE and AF modes as the preview. 1219 setup3AControlsLocked(captureBuilder); 1220 1221 // Set orientation. 1222 int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 1223 captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, 1224 sensorToDeviceRotation(mCharacteristics, rotation)); 1225 1226 // Set request tag to easily track results in callbacks. 1227 captureBuilder.setTag(mRequestCounter.getAndIncrement()); 1228 1229 CaptureRequest request = captureBuilder.build(); 1230 1231 // Create an ImageSaverBuilder in which to collect results, and add it to the queue 1232 // of active requests. 1233 ImageSaver.ImageSaverBuilder jpegBuilder = new ImageSaver.ImageSaverBuilder(activity) 1234 .setCharacteristics(mCharacteristics); 1235 ImageSaver.ImageSaverBuilder rawBuilder = new ImageSaver.ImageSaverBuilder(activity) 1236 .setCharacteristics(mCharacteristics); 1237 1238 mJpegResultQueue.put((int) request.getTag(), jpegBuilder); 1239 mRawResultQueue.put((int) request.getTag(), rawBuilder); 1240 1241 mCaptureSession.capture(request, mCaptureCallback, mBackgroundHandler); 1242 1243 } catch (CameraAccessException e) { 1244 e.printStackTrace(); 1245 } 1246 } 1247 1248 /** 1249 * Called after a RAW/JPEG capture has completed; resets the AF trigger state for the 1250 * pre-capture sequence. 1251 * <p/> 1252 * Call this only with {@link #mCameraStateLock} held. 1253 */ finishedCaptureLocked()1254 private void finishedCaptureLocked() { 1255 try { 1256 // Reset the auto-focus trigger in case AF didn't run quickly enough. 1257 if (!mNoAFRun) { 1258 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 1259 CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); 1260 1261 mCaptureSession.capture(mPreviewRequestBuilder.build(), mPreCaptureCallback, 1262 mBackgroundHandler); 1263 1264 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 1265 CameraMetadata.CONTROL_AF_TRIGGER_IDLE); 1266 } 1267 } catch (CameraAccessException e) { 1268 e.printStackTrace(); 1269 } 1270 } 1271 1272 /** 1273 * Retrieve the next {@link Image} from a reference counted {@link ImageReader}, retaining 1274 * that {@link ImageReader} until that {@link Image} is no longer in use, and set this 1275 * {@link Image} as the result for the next request in the queue of pending requests. If 1276 * all necessary information is available, begin saving the image to a file in a background 1277 * thread. 1278 * 1279 * @param pendingQueue the currently active requests. 1280 * @param reader a reference counted wrapper containing an {@link ImageReader} from which 1281 * to acquire an image. 1282 */ dequeueAndSaveImage(TreeMap<Integer, ImageSaver.ImageSaverBuilder> pendingQueue, RefCountedAutoCloseable<ImageReader> reader)1283 private void dequeueAndSaveImage(TreeMap<Integer, ImageSaver.ImageSaverBuilder> pendingQueue, 1284 RefCountedAutoCloseable<ImageReader> reader) { 1285 synchronized (mCameraStateLock) { 1286 Map.Entry<Integer, ImageSaver.ImageSaverBuilder> entry = 1287 pendingQueue.firstEntry(); 1288 ImageSaver.ImageSaverBuilder builder = entry.getValue(); 1289 1290 // Increment reference count to prevent ImageReader from being closed while we 1291 // are saving its Images in a background thread (otherwise their resources may 1292 // be freed while we are writing to a file). 1293 if (reader == null || reader.getAndRetain() == null) { 1294 Log.e(TAG, "Paused the activity before we could save the image," + 1295 " ImageReader already closed."); 1296 pendingQueue.remove(entry.getKey()); 1297 return; 1298 } 1299 1300 Image image; 1301 try { 1302 image = reader.get().acquireNextImage(); 1303 } catch (IllegalStateException e) { 1304 Log.e(TAG, "Too many images queued for saving, dropping image for request: " + 1305 entry.getKey()); 1306 pendingQueue.remove(entry.getKey()); 1307 return; 1308 } 1309 1310 builder.setRefCountedReader(reader).setImage(image); 1311 1312 handleCompletionLocked(entry.getKey(), builder, pendingQueue); 1313 } 1314 } 1315 1316 /** 1317 * Runnable that saves an {@link Image} into the specified {@link File}, and updates 1318 * {@link android.provider.MediaStore} to include the resulting file. 1319 * <p/> 1320 * This can be constructed through an {@link ImageSaverBuilder} as the necessary image and 1321 * result information becomes available. 1322 */ 1323 private static class ImageSaver implements Runnable { 1324 1325 /** 1326 * The image to save. 1327 */ 1328 private final Image mImage; 1329 /** 1330 * The file we save the image into. 1331 */ 1332 private final File mFile; 1333 1334 /** 1335 * The CaptureResult for this image capture. 1336 */ 1337 private final CaptureResult mCaptureResult; 1338 1339 /** 1340 * The CameraCharacteristics for this camera device. 1341 */ 1342 private final CameraCharacteristics mCharacteristics; 1343 1344 /** 1345 * The Context to use when updating MediaStore with the saved images. 1346 */ 1347 private final Context mContext; 1348 1349 /** 1350 * A reference counted wrapper for the ImageReader that owns the given image. 1351 */ 1352 private final RefCountedAutoCloseable<ImageReader> mReader; 1353 ImageSaver(Image image, File file, CaptureResult result, CameraCharacteristics characteristics, Context context, RefCountedAutoCloseable<ImageReader> reader)1354 private ImageSaver(Image image, File file, CaptureResult result, 1355 CameraCharacteristics characteristics, Context context, 1356 RefCountedAutoCloseable<ImageReader> reader) { 1357 mImage = image; 1358 mFile = file; 1359 mCaptureResult = result; 1360 mCharacteristics = characteristics; 1361 mContext = context; 1362 mReader = reader; 1363 } 1364 1365 @Override run()1366 public void run() { 1367 boolean success = false; 1368 int format = mImage.getFormat(); 1369 switch (format) { 1370 case ImageFormat.JPEG: { 1371 ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); 1372 byte[] bytes = new byte[buffer.remaining()]; 1373 buffer.get(bytes); 1374 FileOutputStream output = null; 1375 try { 1376 output = new FileOutputStream(mFile); 1377 output.write(bytes); 1378 success = true; 1379 } catch (IOException e) { 1380 e.printStackTrace(); 1381 } finally { 1382 mImage.close(); 1383 closeOutput(output); 1384 } 1385 break; 1386 } 1387 case ImageFormat.RAW_SENSOR: { 1388 DngCreator dngCreator = new DngCreator(mCharacteristics, mCaptureResult); 1389 FileOutputStream output = null; 1390 try { 1391 output = new FileOutputStream(mFile); 1392 dngCreator.writeImage(output, mImage); 1393 success = true; 1394 } catch (IOException e) { 1395 e.printStackTrace(); 1396 } finally { 1397 mImage.close(); 1398 closeOutput(output); 1399 } 1400 break; 1401 } 1402 default: { 1403 Log.e(TAG, "Cannot save image, unexpected image format:" + format); 1404 break; 1405 } 1406 } 1407 1408 // Decrement reference count to allow ImageReader to be closed to free up resources. 1409 mReader.close(); 1410 1411 // If saving the file succeeded, update MediaStore. 1412 if (success) { 1413 MediaScannerConnection.scanFile(mContext, new String[]{mFile.getPath()}, 1414 /*mimeTypes*/null, new MediaScannerConnection.MediaScannerConnectionClient() { 1415 @Override 1416 public void onMediaScannerConnected() { 1417 // Do nothing 1418 } 1419 1420 @Override 1421 public void onScanCompleted(String path, Uri uri) { 1422 Log.i(TAG, "Scanned " + path + ":"); 1423 Log.i(TAG, "-> uri=" + uri); 1424 } 1425 }); 1426 } 1427 } 1428 1429 /** 1430 * Builder class for constructing {@link ImageSaver}s. 1431 * <p/> 1432 * This class is thread safe. 1433 */ 1434 public static class ImageSaverBuilder { 1435 private Image mImage; 1436 private File mFile; 1437 private CaptureResult mCaptureResult; 1438 private CameraCharacteristics mCharacteristics; 1439 private Context mContext; 1440 private RefCountedAutoCloseable<ImageReader> mReader; 1441 1442 /** 1443 * Construct a new ImageSaverBuilder using the given {@link Context}. 1444 * 1445 * @param context a {@link Context} to for accessing the 1446 * {@link android.provider.MediaStore}. 1447 */ ImageSaverBuilder(final Context context)1448 public ImageSaverBuilder(final Context context) { 1449 mContext = context; 1450 } 1451 setRefCountedReader( RefCountedAutoCloseable<ImageReader> reader)1452 public synchronized ImageSaverBuilder setRefCountedReader( 1453 RefCountedAutoCloseable<ImageReader> reader) { 1454 if (reader == null) throw new NullPointerException(); 1455 1456 mReader = reader; 1457 return this; 1458 } 1459 setImage(final Image image)1460 public synchronized ImageSaverBuilder setImage(final Image image) { 1461 if (image == null) throw new NullPointerException(); 1462 mImage = image; 1463 return this; 1464 } 1465 setFile(final File file)1466 public synchronized ImageSaverBuilder setFile(final File file) { 1467 if (file == null) throw new NullPointerException(); 1468 mFile = file; 1469 return this; 1470 } 1471 setResult(final CaptureResult result)1472 public synchronized ImageSaverBuilder setResult(final CaptureResult result) { 1473 if (result == null) throw new NullPointerException(); 1474 mCaptureResult = result; 1475 return this; 1476 } 1477 setCharacteristics( final CameraCharacteristics characteristics)1478 public synchronized ImageSaverBuilder setCharacteristics( 1479 final CameraCharacteristics characteristics) { 1480 if (characteristics == null) throw new NullPointerException(); 1481 mCharacteristics = characteristics; 1482 return this; 1483 } 1484 buildIfComplete()1485 public synchronized ImageSaver buildIfComplete() { 1486 if (!isComplete()) { 1487 return null; 1488 } 1489 return new ImageSaver(mImage, mFile, mCaptureResult, mCharacteristics, mContext, 1490 mReader); 1491 } 1492 getSaveLocation()1493 public synchronized String getSaveLocation() { 1494 return (mFile == null) ? "Unknown" : mFile.toString(); 1495 } 1496 isComplete()1497 private boolean isComplete() { 1498 return mImage != null && mFile != null && mCaptureResult != null 1499 && mCharacteristics != null; 1500 } 1501 } 1502 } 1503 1504 // Utility classes and methods: 1505 // ********************************************************************************************* 1506 1507 /** 1508 * Comparator based on area of the given {@link Size} objects. 1509 */ 1510 static class CompareSizesByArea implements Comparator<Size> { 1511 1512 @Override compare(Size lhs, Size rhs)1513 public int compare(Size lhs, Size rhs) { 1514 // We cast here to ensure the multiplications won't overflow 1515 return Long.signum((long) lhs.getWidth() * lhs.getHeight() - 1516 (long) rhs.getWidth() * rhs.getHeight()); 1517 } 1518 1519 } 1520 1521 /** 1522 * A dialog fragment for displaying non-recoverable errors; this {@ling Activity} will be 1523 * finished once the dialog has been acknowledged by the user. 1524 */ 1525 public static class ErrorDialog extends DialogFragment { 1526 1527 private String mErrorMessage; 1528 ErrorDialog()1529 public ErrorDialog() { 1530 mErrorMessage = "Unknown error occurred!"; 1531 } 1532 1533 // Build a dialog with a custom message (Fragments require default constructor). buildErrorDialog(String errorMessage)1534 public static ErrorDialog buildErrorDialog(String errorMessage) { 1535 ErrorDialog dialog = new ErrorDialog(); 1536 dialog.mErrorMessage = errorMessage; 1537 return dialog; 1538 } 1539 1540 @Override onCreateDialog(Bundle savedInstanceState)1541 public Dialog onCreateDialog(Bundle savedInstanceState) { 1542 final Activity activity = getActivity(); 1543 return new AlertDialog.Builder(activity) 1544 .setMessage(mErrorMessage) 1545 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1546 @Override 1547 public void onClick(DialogInterface dialogInterface, int i) { 1548 activity.finish(); 1549 } 1550 }) 1551 .create(); 1552 } 1553 } 1554 1555 /** 1556 * A wrapper for an {@link AutoCloseable} object that implements reference counting to allow 1557 * for resource management. 1558 */ 1559 public static class RefCountedAutoCloseable<T extends AutoCloseable> implements AutoCloseable { 1560 private T mObject; 1561 private long mRefCount = 0; 1562 1563 /** 1564 * Wrap the given object. 1565 * 1566 * @param object an object to wrap. 1567 */ 1568 public RefCountedAutoCloseable(T object) { 1569 if (object == null) throw new NullPointerException(); 1570 mObject = object; 1571 } 1572 1573 /** 1574 * Increment the reference count and return the wrapped object. 1575 * 1576 * @return the wrapped object, or null if the object has been released. 1577 */ 1578 public synchronized T getAndRetain() { 1579 if (mRefCount < 0) { 1580 return null; 1581 } 1582 mRefCount++; 1583 return mObject; 1584 } 1585 1586 /** 1587 * Return the wrapped object. 1588 * 1589 * @return the wrapped object, or null if the object has been released. 1590 */ 1591 public synchronized T get() { 1592 return mObject; 1593 } 1594 1595 /** 1596 * Decrement the reference count and release the wrapped object if there are no other 1597 * users retaining this object. 1598 */ 1599 @Override 1600 public synchronized void close() { 1601 if (mRefCount >= 0) { 1602 mRefCount--; 1603 if (mRefCount < 0) { 1604 try { 1605 mObject.close(); 1606 } catch (Exception e) { 1607 throw new RuntimeException(e); 1608 } finally { 1609 mObject = null; 1610 } 1611 } 1612 } 1613 } 1614 } 1615 1616 /** 1617 * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that 1618 * is at least as large as the respective texture view size, and that is at most as large as the 1619 * respective max size, and whose aspect ratio matches with the specified value. If such size 1620 * doesn't exist, choose the largest one that is at most as large as the respective max size, 1621 * and whose aspect ratio matches with the specified value. 1622 * 1623 * @param choices The list of sizes that the camera supports for the intended output 1624 * class 1625 * @param textureViewWidth The width of the texture view relative to sensor coordinate 1626 * @param textureViewHeight The height of the texture view relative to sensor coordinate 1627 * @param maxWidth The maximum width that can be chosen 1628 * @param maxHeight The maximum height that can be chosen 1629 * @param aspectRatio The aspect ratio 1630 * @return The optimal {@code Size}, or an arbitrary one if none were big enough 1631 */ 1632 private static Size chooseOptimalSize(Size[] choices, int textureViewWidth, 1633 int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) { 1634 // Collect the supported resolutions that are at least as big as the preview Surface 1635 List<Size> bigEnough = new ArrayList<>(); 1636 // Collect the supported resolutions that are smaller than the preview Surface 1637 List<Size> notBigEnough = new ArrayList<>(); 1638 int w = aspectRatio.getWidth(); 1639 int h = aspectRatio.getHeight(); 1640 for (Size option : choices) { 1641 if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight && 1642 option.getHeight() == option.getWidth() * h / w) { 1643 if (option.getWidth() >= textureViewWidth && 1644 option.getHeight() >= textureViewHeight) { 1645 bigEnough.add(option); 1646 } else { 1647 notBigEnough.add(option); 1648 } 1649 } 1650 } 1651 1652 // Pick the smallest of those big enough. If there is no one big enough, pick the 1653 // largest of those not big enough. 1654 if (bigEnough.size() > 0) { 1655 return Collections.min(bigEnough, new CompareSizesByArea()); 1656 } else if (notBigEnough.size() > 0) { 1657 return Collections.max(notBigEnough, new CompareSizesByArea()); 1658 } else { 1659 Log.e(TAG, "Couldn't find any suitable preview size"); 1660 return choices[0]; 1661 } 1662 } 1663 1664 /** 1665 * Generate a string containing a formatted timestamp with the current date and time. 1666 * 1667 * @return a {@link String} representing a time. 1668 */ 1669 private static String generateTimestamp() { 1670 SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SSS", Locale.US); 1671 return sdf.format(new Date()); 1672 } 1673 1674 /** 1675 * Cleanup the given {@link OutputStream}. 1676 * 1677 * @param outputStream the stream to close. 1678 */ 1679 private static void closeOutput(OutputStream outputStream) { 1680 if (null != outputStream) { 1681 try { 1682 outputStream.close(); 1683 } catch (IOException e) { 1684 e.printStackTrace(); 1685 } 1686 } 1687 } 1688 1689 /** 1690 * Return true if the given array contains the given integer. 1691 * 1692 * @param modes array to check. 1693 * @param mode integer to get for. 1694 * @return true if the array contains the given integer, otherwise false. 1695 */ 1696 private static boolean contains(int[] modes, int mode) { 1697 if (modes == null) { 1698 return false; 1699 } 1700 for (int i : modes) { 1701 if (i == mode) { 1702 return true; 1703 } 1704 } 1705 return false; 1706 } 1707 1708 /** 1709 * Return true if the two given {@link Size}s have the same aspect ratio. 1710 * 1711 * @param a first {@link Size} to compare. 1712 * @param b second {@link Size} to compare. 1713 * @return true if the sizes have the same aspect ratio, otherwise false. 1714 */ 1715 private static boolean checkAspectsEqual(Size a, Size b) { 1716 double aAspect = a.getWidth() / (double) a.getHeight(); 1717 double bAspect = b.getWidth() / (double) b.getHeight(); 1718 return Math.abs(aAspect - bAspect) <= ASPECT_RATIO_TOLERANCE; 1719 } 1720 1721 /** 1722 * Rotation need to transform from the camera sensor orientation to the device's current 1723 * orientation. 1724 * 1725 * @param c the {@link CameraCharacteristics} to query for the camera sensor 1726 * orientation. 1727 * @param deviceOrientation the current device orientation relative to the native device 1728 * orientation. 1729 * @return the total rotation from the sensor orientation to the current device orientation. 1730 */ 1731 private static int sensorToDeviceRotation(CameraCharacteristics c, int deviceOrientation) { 1732 int sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION); 1733 1734 // Get device orientation in degrees 1735 deviceOrientation = ORIENTATIONS.get(deviceOrientation); 1736 1737 // Reverse device orientation for front-facing cameras 1738 if (c.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) { 1739 deviceOrientation = -deviceOrientation; 1740 } 1741 1742 // Calculate desired JPEG orientation relative to camera orientation to make 1743 // the image upright relative to the device orientation 1744 return (sensorOrientation + deviceOrientation + 360) % 360; 1745 } 1746 1747 /** 1748 * Shows a {@link Toast} on the UI thread. 1749 * 1750 * @param text The message to show. 1751 */ 1752 private void showToast(String text) { 1753 // We show a Toast by sending request message to mMessageHandler. This makes sure that the 1754 // Toast is shown on the UI thread. 1755 Message message = Message.obtain(); 1756 message.obj = text; 1757 mMessageHandler.sendMessage(message); 1758 } 1759 1760 /** 1761 * If the given request has been completed, remove it from the queue of active requests and 1762 * send an {@link ImageSaver} with the results from this request to a background thread to 1763 * save a file. 1764 * <p/> 1765 * Call this only with {@link #mCameraStateLock} held. 1766 * 1767 * @param requestId the ID of the {@link CaptureRequest} to handle. 1768 * @param builder the {@link ImageSaver.ImageSaverBuilder} for this request. 1769 * @param queue the queue to remove this request from, if completed. 1770 */ 1771 private void handleCompletionLocked(int requestId, ImageSaver.ImageSaverBuilder builder, 1772 TreeMap<Integer, ImageSaver.ImageSaverBuilder> queue) { 1773 if (builder == null) return; 1774 ImageSaver saver = builder.buildIfComplete(); 1775 if (saver != null) { 1776 queue.remove(requestId); 1777 AsyncTask.THREAD_POOL_EXECUTOR.execute(saver); 1778 } 1779 } 1780 1781 /** 1782 * Check if we are using a device that only supports the LEGACY hardware level. 1783 * <p/> 1784 * Call this only with {@link #mCameraStateLock} held. 1785 * 1786 * @return true if this is a legacy device. 1787 */ 1788 private boolean isLegacyLocked() { 1789 return mCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) == 1790 CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; 1791 } 1792 1793 /** 1794 * Start the timer for the pre-capture sequence. 1795 * <p/> 1796 * Call this only with {@link #mCameraStateLock} held. 1797 */ 1798 private void startTimerLocked() { 1799 mCaptureTimer = SystemClock.elapsedRealtime(); 1800 } 1801 1802 /** 1803 * Check if the timer for the pre-capture sequence has been hit. 1804 * <p/> 1805 * Call this only with {@link #mCameraStateLock} held. 1806 * 1807 * @return true if the timeout occurred. 1808 */ 1809 private boolean hitTimeoutLocked() { 1810 return (SystemClock.elapsedRealtime() - mCaptureTimer) > PRECAPTURE_TIMEOUT_MS; 1811 } 1812 1813 /** 1814 * A dialog that explains about the necessary permissions. 1815 */ 1816 public static class PermissionConfirmationDialog extends DialogFragment { 1817 1818 public static PermissionConfirmationDialog newInstance() { 1819 return new PermissionConfirmationDialog(); 1820 } 1821 1822 @Override 1823 public Dialog onCreateDialog(Bundle savedInstanceState) { 1824 final Fragment parent = getParentFragment(); 1825 return new AlertDialog.Builder(getActivity()) 1826 .setMessage(R.string.request_permission) 1827 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1828 @Override 1829 public void onClick(DialogInterface dialog, int which) { 1830 FragmentCompat.requestPermissions(parent, CAMERA_PERMISSIONS, 1831 REQUEST_CAMERA_PERMISSIONS); 1832 } 1833 }) 1834 .setNegativeButton(android.R.string.cancel, 1835 new DialogInterface.OnClickListener() { 1836 @Override 1837 public void onClick(DialogInterface dialog, int which) { 1838 getActivity().finish(); 1839 } 1840 }) 1841 .create(); 1842 } 1843 1844 } 1845 1846 } 1847