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