├── .google └── packaging.yaml ├── Application ├── build.gradle ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── android │ │ │ └── camera2raw │ │ │ ├── AutoFitTextureView.java │ │ │ ├── Camera2RawFragment.java │ │ │ └── CameraActivity.java │ │ └── res │ │ ├── drawable-hdpi │ │ ├── ic_action_info.png │ │ ├── ic_launcher.png │ │ └── tile.9.png │ │ ├── drawable-mdpi │ │ ├── ic_action_info.png │ │ └── ic_launcher.png │ │ ├── drawable-xhdpi │ │ ├── ic_action_info.png │ │ └── ic_launcher.png │ │ ├── drawable-xxhdpi │ │ ├── ic_action_info.png │ │ └── ic_launcher.png │ │ ├── layout-land │ │ └── fragment_camera2_basic.xml │ │ ├── layout │ │ ├── activity_camera.xml │ │ └── fragment_camera2_basic.xml │ │ ├── values-sw600dp │ │ ├── template-dimens.xml │ │ └── template-styles.xml │ │ ├── values-v11 │ │ └── template-styles.xml │ │ ├── values-v21 │ │ ├── base-colors.xml │ │ └── base-template-styles.xml │ │ └── values │ │ ├── base-strings.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ ├── template-dimens.xml │ │ └── template-styles.xml └── tests │ ├── AndroidManifest.xml │ └── src │ └── com │ └── example │ └── android │ └── camera2raw │ └── tests │ └── SampleTests.java ├── CONTRIB.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── packaging.yaml ├── screenshots ├── icon-web.png └── main.png └── settings.gradle /.google/packaging.yaml: -------------------------------------------------------------------------------- 1 | 2 | # GOOGLE SAMPLE PACKAGING DATA 3 | # 4 | # This file is used by Google as part of our samples packaging process. 5 | # End users may safely ignore this file. It has no relevance to other systems. 6 | --- 7 | status: PUBLISHED 8 | technologies: [Android] 9 | categories: [Media, Camera, Camera2] 10 | languages: [Java] 11 | solutions: [Mobile] 12 | github: android-Camera2Raw 13 | level: INTERMEDIATE 14 | icon: screenshots/icon-web.png 15 | apiRefs: 16 | - android:android.hardware.camera2.DngCreator 17 | license: apache2 18 | -------------------------------------------------------------------------------- /Application/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | repositories { 4 | jcenter() 5 | google() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.0.1' 10 | } 11 | } 12 | 13 | apply plugin: 'com.android.application' 14 | 15 | repositories { 16 | jcenter() 17 | google() 18 | } 19 | 20 | dependencies { 21 | compile "com.android.support:support-v4:27.0.2" 22 | compile "com.android.support:support-v13:27.0.2" 23 | compile "com.android.support:cardview-v7:27.0.2" 24 | compile "com.android.support:appcompat-v7:27.0.2" 25 | } 26 | 27 | // The sample build uses multiple directories to 28 | // keep boilerplate and common code separate from 29 | // the main sample code. 30 | List dirs = [ 31 | 'main', // main sample code; look here for the interesting stuff. 32 | 'common', // components that are reused by multiple samples 33 | 'template'] // boilerplate code that is generated by the sample template process 34 | 35 | android { 36 | compileSdkVersion 27 37 | 38 | buildToolsVersion "27.0.2" 39 | 40 | defaultConfig { 41 | minSdkVersion 21 42 | targetSdkVersion 27 43 | } 44 | 45 | compileOptions { 46 | sourceCompatibility JavaVersion.VERSION_1_7 47 | targetCompatibility JavaVersion.VERSION_1_7 48 | } 49 | 50 | sourceSets { 51 | main { 52 | dirs.each { dir -> 53 | java.srcDirs "src/${dir}/java" 54 | res.srcDirs "src/${dir}/res" 55 | } 56 | } 57 | androidTest.setRoot('tests') 58 | androidTest.java.srcDirs = ['tests/src'] 59 | 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /Application/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Application/src/main/java/com/example/android/camera2raw/AutoFitTextureView.java: -------------------------------------------------------------------------------- 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.content.Context; 20 | import android.util.AttributeSet; 21 | import android.view.TextureView; 22 | 23 | /** 24 | * A {@link TextureView} that can be adjusted to a specified aspect ratio. 25 | */ 26 | public class AutoFitTextureView extends TextureView { 27 | 28 | private int mRatioWidth = 0; 29 | private int mRatioHeight = 0; 30 | 31 | public AutoFitTextureView(Context context) { 32 | this(context, null); 33 | } 34 | 35 | public AutoFitTextureView(Context context, AttributeSet attrs) { 36 | this(context, attrs, 0); 37 | } 38 | 39 | public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) { 40 | super(context, attrs, defStyle); 41 | } 42 | 43 | /** 44 | * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio 45 | * calculated from the parameters. Note that the actual sizes of parameters don't matter, that 46 | * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result. 47 | * 48 | * @param width Relative horizontal size 49 | * @param height Relative vertical size 50 | */ 51 | public void setAspectRatio(int width, int height) { 52 | if (width < 0 || height < 0) { 53 | throw new IllegalArgumentException("Size cannot be negative."); 54 | } 55 | if (mRatioWidth == width && mRatioHeight == height) { 56 | return; 57 | } 58 | mRatioWidth = width; 59 | mRatioHeight = height; 60 | requestLayout(); 61 | } 62 | 63 | @Override 64 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 65 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 66 | int width = MeasureSpec.getSize(widthMeasureSpec); 67 | int height = MeasureSpec.getSize(heightMeasureSpec); 68 | if (0 == mRatioWidth || 0 == mRatioHeight) { 69 | setMeasuredDimension(width, height); 70 | } else { 71 | if (width < height * mRatioWidth / mRatioHeight) { 72 | setMeasuredDimension(width, width * mRatioHeight / mRatioWidth); 73 | } else { 74 | setMeasuredDimension(height * mRatioWidth / mRatioHeight, height); 75 | } 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /Application/src/main/java/com/example/android/camera2raw/Camera2RawFragment.java: -------------------------------------------------------------------------------- 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 | *

93 | * In this example, the lifecycle of a single request to take a photo is: 94 | *

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 { 130 | ORIENTATIONS.append(Surface.ROTATION_0, 0); 131 | ORIENTATIONS.append(Surface.ROTATION_90, 90); 132 | ORIENTATIONS.append(Surface.ROTATION_180, 180); 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 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 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 mJpegResultQueue = new TreeMap<>(); 325 | 326 | /** 327 | * Request ID to {@link ImageSaver.ImageSaverBuilder} mapping for in-progress RAW captures. 328 | */ 329 | private final TreeMap 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 (jpegBuilder != null) { 551 | jpegBuilder.setResult(result); 552 | sb.append("Saving JPEG as: "); 553 | sb.append(jpegBuilder.getSaveLocation()); 554 | } 555 | if (rawBuilder != null) { 556 | rawBuilder.setResult(result); 557 | if (jpegBuilder != null) sb.append(", "); 558 | sb.append("Saving RAW as: "); 559 | sb.append(rawBuilder.getSaveLocation()); 560 | } 561 | 562 | // If we have all the results necessary, save the image to a file in the background. 563 | handleCompletionLocked(requestId, jpegBuilder, mJpegResultQueue); 564 | handleCompletionLocked(requestId, rawBuilder, mRawResultQueue); 565 | 566 | finishedCaptureLocked(); 567 | } 568 | 569 | showToast(sb.toString()); 570 | } 571 | 572 | @Override 573 | public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, 574 | CaptureFailure failure) { 575 | int requestId = (int) request.getTag(); 576 | synchronized (mCameraStateLock) { 577 | mJpegResultQueue.remove(requestId); 578 | mRawResultQueue.remove(requestId); 579 | finishedCaptureLocked(); 580 | } 581 | showToast("Capture failed!"); 582 | } 583 | 584 | }; 585 | 586 | /** 587 | * A {@link Handler} for showing {@link Toast}s on the UI thread. 588 | */ 589 | private final Handler mMessageHandler = new Handler(Looper.getMainLooper()) { 590 | @Override 591 | public void handleMessage(Message msg) { 592 | Activity activity = getActivity(); 593 | if (activity != null) { 594 | Toast.makeText(activity, (String) msg.obj, Toast.LENGTH_SHORT).show(); 595 | } 596 | } 597 | }; 598 | 599 | public static Camera2RawFragment newInstance() { 600 | return new Camera2RawFragment(); 601 | } 602 | 603 | @Override 604 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 605 | Bundle savedInstanceState) { 606 | return inflater.inflate(R.layout.fragment_camera2_basic, container, false); 607 | } 608 | 609 | @Override 610 | public void onViewCreated(final View view, Bundle savedInstanceState) { 611 | view.findViewById(R.id.picture).setOnClickListener(this); 612 | view.findViewById(R.id.info).setOnClickListener(this); 613 | mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture); 614 | 615 | // Setup a new OrientationEventListener. This is used to handle rotation events like a 616 | // 180 degree rotation that do not normally trigger a call to onCreate to do view re-layout 617 | // or otherwise cause the preview TextureView's size to change. 618 | mOrientationListener = new OrientationEventListener(getActivity(), 619 | SensorManager.SENSOR_DELAY_NORMAL) { 620 | @Override 621 | public void onOrientationChanged(int orientation) { 622 | if (mTextureView != null && mTextureView.isAvailable()) { 623 | configureTransform(mTextureView.getWidth(), mTextureView.getHeight()); 624 | } 625 | } 626 | }; 627 | } 628 | 629 | @Override 630 | public void onResume() { 631 | super.onResume(); 632 | startBackgroundThread(); 633 | openCamera(); 634 | 635 | // When the screen is turned off and turned back on, the SurfaceTexture is already 636 | // available, and "onSurfaceTextureAvailable" will not be called. In that case, we should 637 | // configure the preview bounds here (otherwise, we wait until the surface is ready in 638 | // the SurfaceTextureListener). 639 | if (mTextureView.isAvailable()) { 640 | configureTransform(mTextureView.getWidth(), mTextureView.getHeight()); 641 | } else { 642 | mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); 643 | } 644 | if (mOrientationListener != null && mOrientationListener.canDetectOrientation()) { 645 | mOrientationListener.enable(); 646 | } 647 | } 648 | 649 | @Override 650 | public void onPause() { 651 | if (mOrientationListener != null) { 652 | mOrientationListener.disable(); 653 | } 654 | closeCamera(); 655 | stopBackgroundThread(); 656 | super.onPause(); 657 | } 658 | 659 | @Override 660 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 661 | if (requestCode == REQUEST_CAMERA_PERMISSIONS) { 662 | for (int result : grantResults) { 663 | if (result != PackageManager.PERMISSION_GRANTED) { 664 | showMissingPermissionError(); 665 | return; 666 | } 667 | } 668 | } else { 669 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 670 | } 671 | } 672 | 673 | @Override 674 | public void onClick(View view) { 675 | switch (view.getId()) { 676 | case R.id.picture: { 677 | takePicture(); 678 | break; 679 | } 680 | case R.id.info: { 681 | Activity activity = getActivity(); 682 | if (null != activity) { 683 | new AlertDialog.Builder(activity) 684 | .setMessage(R.string.intro_message) 685 | .setPositiveButton(android.R.string.ok, null) 686 | .show(); 687 | } 688 | break; 689 | } 690 | } 691 | } 692 | 693 | /** 694 | * Sets up state related to camera that is needed before opening a {@link CameraDevice}. 695 | */ 696 | private boolean setUpCameraOutputs() { 697 | Activity activity = getActivity(); 698 | CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); 699 | if (manager == null) { 700 | ErrorDialog.buildErrorDialog("This device doesn't support Camera2 API."). 701 | show(getFragmentManager(), "dialog"); 702 | return false; 703 | } 704 | try { 705 | // Find a CameraDevice that supports RAW captures, and configure state. 706 | for (String cameraId : manager.getCameraIdList()) { 707 | CameraCharacteristics characteristics 708 | = manager.getCameraCharacteristics(cameraId); 709 | 710 | // We only use a camera that supports RAW in this sample. 711 | if (!contains(characteristics.get( 712 | CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES), 713 | CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) { 714 | continue; 715 | } 716 | 717 | StreamConfigurationMap map = characteristics.get( 718 | CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 719 | 720 | // For still image captures, we use the largest available size. 721 | Size largestJpeg = Collections.max( 722 | Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), 723 | new CompareSizesByArea()); 724 | 725 | Size largestRaw = Collections.max( 726 | Arrays.asList(map.getOutputSizes(ImageFormat.RAW_SENSOR)), 727 | new CompareSizesByArea()); 728 | 729 | synchronized (mCameraStateLock) { 730 | // Set up ImageReaders for JPEG and RAW outputs. Place these in a reference 731 | // counted wrapper to ensure they are only closed when all background tasks 732 | // using them are finished. 733 | if (mJpegImageReader == null || mJpegImageReader.getAndRetain() == null) { 734 | mJpegImageReader = new RefCountedAutoCloseable<>( 735 | ImageReader.newInstance(largestJpeg.getWidth(), 736 | largestJpeg.getHeight(), ImageFormat.JPEG, /*maxImages*/5)); 737 | } 738 | mJpegImageReader.get().setOnImageAvailableListener( 739 | mOnJpegImageAvailableListener, mBackgroundHandler); 740 | 741 | if (mRawImageReader == null || mRawImageReader.getAndRetain() == null) { 742 | mRawImageReader = new RefCountedAutoCloseable<>( 743 | ImageReader.newInstance(largestRaw.getWidth(), 744 | largestRaw.getHeight(), ImageFormat.RAW_SENSOR, /*maxImages*/ 5)); 745 | } 746 | mRawImageReader.get().setOnImageAvailableListener( 747 | mOnRawImageAvailableListener, mBackgroundHandler); 748 | 749 | mCharacteristics = characteristics; 750 | mCameraId = cameraId; 751 | } 752 | return true; 753 | } 754 | } catch (CameraAccessException e) { 755 | e.printStackTrace(); 756 | } 757 | 758 | // If we found no suitable cameras for capturing RAW, warn the user. 759 | ErrorDialog.buildErrorDialog("This device doesn't support capturing RAW photos"). 760 | show(getFragmentManager(), "dialog"); 761 | return false; 762 | } 763 | 764 | /** 765 | * Opens the camera specified by {@link #mCameraId}. 766 | */ 767 | @SuppressWarnings("MissingPermission") 768 | private void openCamera() { 769 | if (!setUpCameraOutputs()) { 770 | return; 771 | } 772 | if (!hasAllPermissionsGranted()) { 773 | requestCameraPermissions(); 774 | return; 775 | } 776 | 777 | Activity activity = getActivity(); 778 | CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); 779 | try { 780 | // Wait for any previously running session to finish. 781 | if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { 782 | throw new RuntimeException("Time out waiting to lock camera opening."); 783 | } 784 | 785 | String cameraId; 786 | Handler backgroundHandler; 787 | synchronized (mCameraStateLock) { 788 | cameraId = mCameraId; 789 | backgroundHandler = mBackgroundHandler; 790 | } 791 | 792 | // Attempt to open the camera. mStateCallback will be called on the background handler's 793 | // thread when this succeeds or fails. 794 | manager.openCamera(cameraId, mStateCallback, backgroundHandler); 795 | } catch (CameraAccessException e) { 796 | e.printStackTrace(); 797 | } catch (InterruptedException e) { 798 | throw new RuntimeException("Interrupted while trying to lock camera opening.", e); 799 | } 800 | } 801 | 802 | /** 803 | * Requests permissions necessary to use camera and save pictures. 804 | */ 805 | private void requestCameraPermissions() { 806 | if (shouldShowRationale()) { 807 | PermissionConfirmationDialog.newInstance().show(getChildFragmentManager(), "dialog"); 808 | } else { 809 | FragmentCompat.requestPermissions(this, CAMERA_PERMISSIONS, REQUEST_CAMERA_PERMISSIONS); 810 | } 811 | } 812 | 813 | /** 814 | * Tells whether all the necessary permissions are granted to this app. 815 | * 816 | * @return True if all the required permissions are granted. 817 | */ 818 | private boolean hasAllPermissionsGranted() { 819 | for (String permission : CAMERA_PERMISSIONS) { 820 | if (ActivityCompat.checkSelfPermission(getActivity(), permission) 821 | != PackageManager.PERMISSION_GRANTED) { 822 | return false; 823 | } 824 | } 825 | return true; 826 | } 827 | 828 | /** 829 | * Gets whether you should show UI with rationale for requesting the permissions. 830 | * 831 | * @return True if the UI should be shown. 832 | */ 833 | private boolean shouldShowRationale() { 834 | for (String permission : CAMERA_PERMISSIONS) { 835 | if (FragmentCompat.shouldShowRequestPermissionRationale(this, permission)) { 836 | return true; 837 | } 838 | } 839 | return false; 840 | } 841 | 842 | /** 843 | * Shows that this app really needs the permission and finishes the app. 844 | */ 845 | private void showMissingPermissionError() { 846 | Activity activity = getActivity(); 847 | if (activity != null) { 848 | Toast.makeText(activity, R.string.request_permission, Toast.LENGTH_SHORT).show(); 849 | activity.finish(); 850 | } 851 | } 852 | 853 | /** 854 | * Closes the current {@link CameraDevice}. 855 | */ 856 | private void closeCamera() { 857 | try { 858 | mCameraOpenCloseLock.acquire(); 859 | synchronized (mCameraStateLock) { 860 | 861 | // Reset state and clean up resources used by the camera. 862 | // Note: After calling this, the ImageReaders will be closed after any background 863 | // tasks saving Images from these readers have been completed. 864 | mPendingUserCaptures = 0; 865 | mState = STATE_CLOSED; 866 | if (null != mCaptureSession) { 867 | mCaptureSession.close(); 868 | mCaptureSession = null; 869 | } 870 | if (null != mCameraDevice) { 871 | mCameraDevice.close(); 872 | mCameraDevice = null; 873 | } 874 | if (null != mJpegImageReader) { 875 | mJpegImageReader.close(); 876 | mJpegImageReader = null; 877 | } 878 | if (null != mRawImageReader) { 879 | mRawImageReader.close(); 880 | mRawImageReader = null; 881 | } 882 | } 883 | } catch (InterruptedException e) { 884 | throw new RuntimeException("Interrupted while trying to lock camera closing.", e); 885 | } finally { 886 | mCameraOpenCloseLock.release(); 887 | } 888 | } 889 | 890 | /** 891 | * Starts a background thread and its {@link Handler}. 892 | */ 893 | private void startBackgroundThread() { 894 | mBackgroundThread = new HandlerThread("CameraBackground"); 895 | mBackgroundThread.start(); 896 | synchronized (mCameraStateLock) { 897 | mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); 898 | } 899 | } 900 | 901 | /** 902 | * Stops the background thread and its {@link Handler}. 903 | */ 904 | private void stopBackgroundThread() { 905 | mBackgroundThread.quitSafely(); 906 | try { 907 | mBackgroundThread.join(); 908 | mBackgroundThread = null; 909 | synchronized (mCameraStateLock) { 910 | mBackgroundHandler = null; 911 | } 912 | } catch (InterruptedException e) { 913 | e.printStackTrace(); 914 | } 915 | } 916 | 917 | /** 918 | * Creates a new {@link CameraCaptureSession} for camera preview. 919 | *

920 | * Call this only with {@link #mCameraStateLock} held. 921 | */ 922 | private void createCameraPreviewSessionLocked() { 923 | try { 924 | SurfaceTexture texture = mTextureView.getSurfaceTexture(); 925 | // We configure the size of default buffer to be the size of camera preview we want. 926 | texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); 927 | 928 | // This is the output Surface we need to start preview. 929 | Surface surface = new Surface(texture); 930 | 931 | // We set up a CaptureRequest.Builder with the output Surface. 932 | mPreviewRequestBuilder 933 | = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 934 | mPreviewRequestBuilder.addTarget(surface); 935 | 936 | // Here, we create a CameraCaptureSession for camera preview. 937 | mCameraDevice.createCaptureSession(Arrays.asList(surface, 938 | mJpegImageReader.get().getSurface(), 939 | mRawImageReader.get().getSurface()), new CameraCaptureSession.StateCallback() { 940 | @Override 941 | public void onConfigured(CameraCaptureSession cameraCaptureSession) { 942 | synchronized (mCameraStateLock) { 943 | // The camera is already closed 944 | if (null == mCameraDevice) { 945 | return; 946 | } 947 | 948 | try { 949 | setup3AControlsLocked(mPreviewRequestBuilder); 950 | // Finally, we start displaying the camera preview. 951 | cameraCaptureSession.setRepeatingRequest( 952 | mPreviewRequestBuilder.build(), 953 | mPreCaptureCallback, mBackgroundHandler); 954 | mState = STATE_PREVIEW; 955 | } catch (CameraAccessException | IllegalStateException e) { 956 | e.printStackTrace(); 957 | return; 958 | } 959 | // When the session is ready, we start displaying the preview. 960 | mCaptureSession = cameraCaptureSession; 961 | } 962 | } 963 | 964 | @Override 965 | public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { 966 | showToast("Failed to configure camera."); 967 | } 968 | }, mBackgroundHandler 969 | ); 970 | } catch (CameraAccessException e) { 971 | e.printStackTrace(); 972 | } 973 | } 974 | 975 | /** 976 | * Configure the given {@link CaptureRequest.Builder} to use auto-focus, auto-exposure, and 977 | * auto-white-balance controls if available. 978 | *

979 | * Call this only with {@link #mCameraStateLock} held. 980 | * 981 | * @param builder the builder to configure. 982 | */ 983 | private void setup3AControlsLocked(CaptureRequest.Builder builder) { 984 | // Enable auto-magical 3A run by camera device 985 | builder.set(CaptureRequest.CONTROL_MODE, 986 | CaptureRequest.CONTROL_MODE_AUTO); 987 | 988 | Float minFocusDist = 989 | mCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE); 990 | 991 | // If MINIMUM_FOCUS_DISTANCE is 0, lens is fixed-focus and we need to skip the AF run. 992 | mNoAFRun = (minFocusDist == null || minFocusDist == 0); 993 | 994 | if (!mNoAFRun) { 995 | // If there is a "continuous picture" mode available, use it, otherwise default to AUTO. 996 | if (contains(mCharacteristics.get( 997 | CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES), 998 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)) { 999 | builder.set(CaptureRequest.CONTROL_AF_MODE, 1000 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 1001 | } else { 1002 | builder.set(CaptureRequest.CONTROL_AF_MODE, 1003 | CaptureRequest.CONTROL_AF_MODE_AUTO); 1004 | } 1005 | } 1006 | 1007 | // If there is an auto-magical flash control mode available, use it, otherwise default to 1008 | // the "on" mode, which is guaranteed to always be available. 1009 | if (contains(mCharacteristics.get( 1010 | CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES), 1011 | CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)) { 1012 | builder.set(CaptureRequest.CONTROL_AE_MODE, 1013 | CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); 1014 | } else { 1015 | builder.set(CaptureRequest.CONTROL_AE_MODE, 1016 | CaptureRequest.CONTROL_AE_MODE_ON); 1017 | } 1018 | 1019 | // If there is an auto-magical white balance control mode available, use it. 1020 | if (contains(mCharacteristics.get( 1021 | CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES), 1022 | CaptureRequest.CONTROL_AWB_MODE_AUTO)) { 1023 | // Allow AWB to run auto-magically if this device supports this 1024 | builder.set(CaptureRequest.CONTROL_AWB_MODE, 1025 | CaptureRequest.CONTROL_AWB_MODE_AUTO); 1026 | } 1027 | } 1028 | 1029 | /** 1030 | * Configure the necessary {@link android.graphics.Matrix} transformation to `mTextureView`, 1031 | * and start/restart the preview capture session if necessary. 1032 | *

1033 | * This method should be called after the camera state has been initialized in 1034 | * setUpCameraOutputs. 1035 | * 1036 | * @param viewWidth The width of `mTextureView` 1037 | * @param viewHeight The height of `mTextureView` 1038 | */ 1039 | private void configureTransform(int viewWidth, int viewHeight) { 1040 | Activity activity = getActivity(); 1041 | synchronized (mCameraStateLock) { 1042 | if (null == mTextureView || null == activity) { 1043 | return; 1044 | } 1045 | 1046 | StreamConfigurationMap map = mCharacteristics.get( 1047 | CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 1048 | 1049 | // For still image captures, we always use the largest available size. 1050 | Size largestJpeg = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), 1051 | new CompareSizesByArea()); 1052 | 1053 | // Find the rotation of the device relative to the native device orientation. 1054 | int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 1055 | Point displaySize = new Point(); 1056 | activity.getWindowManager().getDefaultDisplay().getSize(displaySize); 1057 | 1058 | // Find the rotation of the device relative to the camera sensor's orientation. 1059 | int totalRotation = sensorToDeviceRotation(mCharacteristics, deviceRotation); 1060 | 1061 | // Swap the view dimensions for calculation as needed if they are rotated relative to 1062 | // the sensor. 1063 | boolean swappedDimensions = totalRotation == 90 || totalRotation == 270; 1064 | int rotatedViewWidth = viewWidth; 1065 | int rotatedViewHeight = viewHeight; 1066 | int maxPreviewWidth = displaySize.x; 1067 | int maxPreviewHeight = displaySize.y; 1068 | 1069 | if (swappedDimensions) { 1070 | rotatedViewWidth = viewHeight; 1071 | rotatedViewHeight = viewWidth; 1072 | maxPreviewWidth = displaySize.y; 1073 | maxPreviewHeight = displaySize.x; 1074 | } 1075 | 1076 | // Preview should not be larger than display size and 1080p. 1077 | if (maxPreviewWidth > MAX_PREVIEW_WIDTH) { 1078 | maxPreviewWidth = MAX_PREVIEW_WIDTH; 1079 | } 1080 | 1081 | if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) { 1082 | maxPreviewHeight = MAX_PREVIEW_HEIGHT; 1083 | } 1084 | 1085 | // Find the best preview size for these view dimensions and configured JPEG size. 1086 | Size previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), 1087 | rotatedViewWidth, rotatedViewHeight, maxPreviewWidth, maxPreviewHeight, 1088 | largestJpeg); 1089 | 1090 | if (swappedDimensions) { 1091 | mTextureView.setAspectRatio( 1092 | previewSize.getHeight(), previewSize.getWidth()); 1093 | } else { 1094 | mTextureView.setAspectRatio( 1095 | previewSize.getWidth(), previewSize.getHeight()); 1096 | } 1097 | 1098 | // Find rotation of device in degrees (reverse device orientation for front-facing 1099 | // cameras). 1100 | int rotation = (mCharacteristics.get(CameraCharacteristics.LENS_FACING) == 1101 | CameraCharacteristics.LENS_FACING_FRONT) ? 1102 | (360 + ORIENTATIONS.get(deviceRotation)) % 360 : 1103 | (360 - ORIENTATIONS.get(deviceRotation)) % 360; 1104 | 1105 | Matrix matrix = new Matrix(); 1106 | RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); 1107 | RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth()); 1108 | float centerX = viewRect.centerX(); 1109 | float centerY = viewRect.centerY(); 1110 | 1111 | // Initially, output stream images from the Camera2 API will be rotated to the native 1112 | // device orientation from the sensor's orientation, and the TextureView will default to 1113 | // scaling these buffers to fill it's view bounds. If the aspect ratios and relative 1114 | // orientations are correct, this is fine. 1115 | // 1116 | // However, if the device orientation has been rotated relative to its native 1117 | // orientation so that the TextureView's dimensions are swapped relative to the 1118 | // native device orientation, we must do the following to ensure the output stream 1119 | // images are not incorrectly scaled by the TextureView: 1120 | // - Undo the scale-to-fill from the output buffer's dimensions (i.e. its dimensions 1121 | // in the native device orientation) to the TextureView's dimension. 1122 | // - Apply a scale-to-fill from the output buffer's rotated dimensions 1123 | // (i.e. its dimensions in the current device orientation) to the TextureView's 1124 | // dimensions. 1125 | // - Apply the rotation from the native device orientation to the current device 1126 | // rotation. 1127 | if (Surface.ROTATION_90 == deviceRotation || Surface.ROTATION_270 == deviceRotation) { 1128 | bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); 1129 | matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); 1130 | float scale = Math.max( 1131 | (float) viewHeight / previewSize.getHeight(), 1132 | (float) viewWidth / previewSize.getWidth()); 1133 | matrix.postScale(scale, scale, centerX, centerY); 1134 | 1135 | } 1136 | matrix.postRotate(rotation, centerX, centerY); 1137 | 1138 | mTextureView.setTransform(matrix); 1139 | 1140 | // Start or restart the active capture session if the preview was initialized or 1141 | // if its aspect ratio changed significantly. 1142 | if (mPreviewSize == null || !checkAspectsEqual(previewSize, mPreviewSize)) { 1143 | mPreviewSize = previewSize; 1144 | if (mState != STATE_CLOSED) { 1145 | createCameraPreviewSessionLocked(); 1146 | } 1147 | } 1148 | } 1149 | } 1150 | 1151 | /** 1152 | * Initiate a still image capture. 1153 | *

1154 | * This function sends a capture request that initiates a pre-capture sequence in our state 1155 | * machine that waits for auto-focus to finish, ending in a "locked" state where the lens is no 1156 | * longer moving, waits for auto-exposure to choose a good exposure value, and waits for 1157 | * auto-white-balance to converge. 1158 | */ 1159 | private void takePicture() { 1160 | synchronized (mCameraStateLock) { 1161 | mPendingUserCaptures++; 1162 | 1163 | // If we already triggered a pre-capture sequence, or are in a state where we cannot 1164 | // do this, return immediately. 1165 | if (mState != STATE_PREVIEW) { 1166 | return; 1167 | } 1168 | 1169 | try { 1170 | // Trigger an auto-focus run if camera is capable. If the camera is already focused, 1171 | // this should do nothing. 1172 | if (!mNoAFRun) { 1173 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 1174 | CameraMetadata.CONTROL_AF_TRIGGER_START); 1175 | } 1176 | 1177 | // If this is not a legacy device, we can also trigger an auto-exposure metering 1178 | // run. 1179 | if (!isLegacyLocked()) { 1180 | // Tell the camera to lock focus. 1181 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, 1182 | CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START); 1183 | } 1184 | 1185 | // Update state machine to wait for auto-focus, auto-exposure, and 1186 | // auto-white-balance (aka. "3A") to converge. 1187 | mState = STATE_WAITING_FOR_3A_CONVERGENCE; 1188 | 1189 | // Start a timer for the pre-capture sequence. 1190 | startTimerLocked(); 1191 | 1192 | // Replace the existing repeating request with one with updated 3A triggers. 1193 | mCaptureSession.capture(mPreviewRequestBuilder.build(), mPreCaptureCallback, 1194 | mBackgroundHandler); 1195 | } catch (CameraAccessException e) { 1196 | e.printStackTrace(); 1197 | } 1198 | } 1199 | } 1200 | 1201 | /** 1202 | * Send a capture request to the camera device that initiates a capture targeting the JPEG and 1203 | * RAW outputs. 1204 | *

1205 | * Call this only with {@link #mCameraStateLock} held. 1206 | */ 1207 | private void captureStillPictureLocked() { 1208 | try { 1209 | final Activity activity = getActivity(); 1210 | if (null == activity || null == mCameraDevice) { 1211 | return; 1212 | } 1213 | // This is the CaptureRequest.Builder that we use to take a picture. 1214 | final CaptureRequest.Builder captureBuilder = 1215 | mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); 1216 | 1217 | captureBuilder.addTarget(mJpegImageReader.get().getSurface()); 1218 | captureBuilder.addTarget(mRawImageReader.get().getSurface()); 1219 | 1220 | // Use the same AE and AF modes as the preview. 1221 | setup3AControlsLocked(captureBuilder); 1222 | 1223 | // Set orientation. 1224 | int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 1225 | captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, 1226 | sensorToDeviceRotation(mCharacteristics, rotation)); 1227 | 1228 | // Set request tag to easily track results in callbacks. 1229 | captureBuilder.setTag(mRequestCounter.getAndIncrement()); 1230 | 1231 | CaptureRequest request = captureBuilder.build(); 1232 | 1233 | // Create an ImageSaverBuilder in which to collect results, and add it to the queue 1234 | // of active requests. 1235 | ImageSaver.ImageSaverBuilder jpegBuilder = new ImageSaver.ImageSaverBuilder(activity) 1236 | .setCharacteristics(mCharacteristics); 1237 | ImageSaver.ImageSaverBuilder rawBuilder = new ImageSaver.ImageSaverBuilder(activity) 1238 | .setCharacteristics(mCharacteristics); 1239 | 1240 | mJpegResultQueue.put((int) request.getTag(), jpegBuilder); 1241 | mRawResultQueue.put((int) request.getTag(), rawBuilder); 1242 | 1243 | mCaptureSession.capture(request, mCaptureCallback, mBackgroundHandler); 1244 | 1245 | } catch (CameraAccessException e) { 1246 | e.printStackTrace(); 1247 | } 1248 | } 1249 | 1250 | /** 1251 | * Called after a RAW/JPEG capture has completed; resets the AF trigger state for the 1252 | * pre-capture sequence. 1253 | *

1254 | * Call this only with {@link #mCameraStateLock} held. 1255 | */ 1256 | private void finishedCaptureLocked() { 1257 | try { 1258 | // Reset the auto-focus trigger in case AF didn't run quickly enough. 1259 | if (!mNoAFRun) { 1260 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 1261 | CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); 1262 | 1263 | mCaptureSession.capture(mPreviewRequestBuilder.build(), mPreCaptureCallback, 1264 | mBackgroundHandler); 1265 | 1266 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 1267 | CameraMetadata.CONTROL_AF_TRIGGER_IDLE); 1268 | } 1269 | } catch (CameraAccessException e) { 1270 | e.printStackTrace(); 1271 | } 1272 | } 1273 | 1274 | /** 1275 | * Retrieve the next {@link Image} from a reference counted {@link ImageReader}, retaining 1276 | * that {@link ImageReader} until that {@link Image} is no longer in use, and set this 1277 | * {@link Image} as the result for the next request in the queue of pending requests. If 1278 | * all necessary information is available, begin saving the image to a file in a background 1279 | * thread. 1280 | * 1281 | * @param pendingQueue the currently active requests. 1282 | * @param reader a reference counted wrapper containing an {@link ImageReader} from which 1283 | * to acquire an image. 1284 | */ 1285 | private void dequeueAndSaveImage(TreeMap pendingQueue, 1286 | RefCountedAutoCloseable reader) { 1287 | synchronized (mCameraStateLock) { 1288 | Map.Entry entry = 1289 | pendingQueue.firstEntry(); 1290 | ImageSaver.ImageSaverBuilder builder = entry.getValue(); 1291 | 1292 | // Increment reference count to prevent ImageReader from being closed while we 1293 | // are saving its Images in a background thread (otherwise their resources may 1294 | // be freed while we are writing to a file). 1295 | if (reader == null || reader.getAndRetain() == null) { 1296 | Log.e(TAG, "Paused the activity before we could save the image," + 1297 | " ImageReader already closed."); 1298 | pendingQueue.remove(entry.getKey()); 1299 | return; 1300 | } 1301 | 1302 | Image image; 1303 | try { 1304 | image = reader.get().acquireNextImage(); 1305 | } catch (IllegalStateException e) { 1306 | Log.e(TAG, "Too many images queued for saving, dropping image for request: " + 1307 | entry.getKey()); 1308 | pendingQueue.remove(entry.getKey()); 1309 | return; 1310 | } 1311 | 1312 | builder.setRefCountedReader(reader).setImage(image); 1313 | 1314 | handleCompletionLocked(entry.getKey(), builder, pendingQueue); 1315 | } 1316 | } 1317 | 1318 | /** 1319 | * Runnable that saves an {@link Image} into the specified {@link File}, and updates 1320 | * {@link android.provider.MediaStore} to include the resulting file. 1321 | *

1322 | * This can be constructed through an {@link ImageSaverBuilder} as the necessary image and 1323 | * result information becomes available. 1324 | */ 1325 | private static class ImageSaver implements Runnable { 1326 | 1327 | /** 1328 | * The image to save. 1329 | */ 1330 | private final Image mImage; 1331 | /** 1332 | * The file we save the image into. 1333 | */ 1334 | private final File mFile; 1335 | 1336 | /** 1337 | * The CaptureResult for this image capture. 1338 | */ 1339 | private final CaptureResult mCaptureResult; 1340 | 1341 | /** 1342 | * The CameraCharacteristics for this camera device. 1343 | */ 1344 | private final CameraCharacteristics mCharacteristics; 1345 | 1346 | /** 1347 | * The Context to use when updating MediaStore with the saved images. 1348 | */ 1349 | private final Context mContext; 1350 | 1351 | /** 1352 | * A reference counted wrapper for the ImageReader that owns the given image. 1353 | */ 1354 | private final RefCountedAutoCloseable mReader; 1355 | 1356 | private ImageSaver(Image image, File file, CaptureResult result, 1357 | CameraCharacteristics characteristics, Context context, 1358 | RefCountedAutoCloseable reader) { 1359 | mImage = image; 1360 | mFile = file; 1361 | mCaptureResult = result; 1362 | mCharacteristics = characteristics; 1363 | mContext = context; 1364 | mReader = reader; 1365 | } 1366 | 1367 | @Override 1368 | public void run() { 1369 | boolean success = false; 1370 | int format = mImage.getFormat(); 1371 | switch (format) { 1372 | case ImageFormat.JPEG: { 1373 | ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); 1374 | byte[] bytes = new byte[buffer.remaining()]; 1375 | buffer.get(bytes); 1376 | FileOutputStream output = null; 1377 | try { 1378 | output = new FileOutputStream(mFile); 1379 | output.write(bytes); 1380 | success = true; 1381 | } catch (IOException e) { 1382 | e.printStackTrace(); 1383 | } finally { 1384 | mImage.close(); 1385 | closeOutput(output); 1386 | } 1387 | break; 1388 | } 1389 | case ImageFormat.RAW_SENSOR: { 1390 | DngCreator dngCreator = new DngCreator(mCharacteristics, mCaptureResult); 1391 | FileOutputStream output = null; 1392 | try { 1393 | output = new FileOutputStream(mFile); 1394 | dngCreator.writeImage(output, mImage); 1395 | success = true; 1396 | } catch (IOException e) { 1397 | e.printStackTrace(); 1398 | } finally { 1399 | mImage.close(); 1400 | closeOutput(output); 1401 | } 1402 | break; 1403 | } 1404 | default: { 1405 | Log.e(TAG, "Cannot save image, unexpected image format:" + format); 1406 | break; 1407 | } 1408 | } 1409 | 1410 | // Decrement reference count to allow ImageReader to be closed to free up resources. 1411 | mReader.close(); 1412 | 1413 | // If saving the file succeeded, update MediaStore. 1414 | if (success) { 1415 | MediaScannerConnection.scanFile(mContext, new String[]{mFile.getPath()}, 1416 | /*mimeTypes*/null, new MediaScannerConnection.MediaScannerConnectionClient() { 1417 | @Override 1418 | public void onMediaScannerConnected() { 1419 | // Do nothing 1420 | } 1421 | 1422 | @Override 1423 | public void onScanCompleted(String path, Uri uri) { 1424 | Log.i(TAG, "Scanned " + path + ":"); 1425 | Log.i(TAG, "-> uri=" + uri); 1426 | } 1427 | }); 1428 | } 1429 | } 1430 | 1431 | /** 1432 | * Builder class for constructing {@link ImageSaver}s. 1433 | *

1434 | * This class is thread safe. 1435 | */ 1436 | public static class ImageSaverBuilder { 1437 | private Image mImage; 1438 | private File mFile; 1439 | private CaptureResult mCaptureResult; 1440 | private CameraCharacteristics mCharacteristics; 1441 | private Context mContext; 1442 | private RefCountedAutoCloseable mReader; 1443 | 1444 | /** 1445 | * Construct a new ImageSaverBuilder using the given {@link Context}. 1446 | * 1447 | * @param context a {@link Context} to for accessing the 1448 | * {@link android.provider.MediaStore}. 1449 | */ 1450 | public ImageSaverBuilder(final Context context) { 1451 | mContext = context; 1452 | } 1453 | 1454 | public synchronized ImageSaverBuilder setRefCountedReader( 1455 | RefCountedAutoCloseable reader) { 1456 | if (reader == null) throw new NullPointerException(); 1457 | 1458 | mReader = reader; 1459 | return this; 1460 | } 1461 | 1462 | public synchronized ImageSaverBuilder setImage(final Image image) { 1463 | if (image == null) throw new NullPointerException(); 1464 | mImage = image; 1465 | return this; 1466 | } 1467 | 1468 | public synchronized ImageSaverBuilder setFile(final File file) { 1469 | if (file == null) throw new NullPointerException(); 1470 | mFile = file; 1471 | return this; 1472 | } 1473 | 1474 | public synchronized ImageSaverBuilder setResult(final CaptureResult result) { 1475 | if (result == null) throw new NullPointerException(); 1476 | mCaptureResult = result; 1477 | return this; 1478 | } 1479 | 1480 | public synchronized ImageSaverBuilder setCharacteristics( 1481 | final CameraCharacteristics characteristics) { 1482 | if (characteristics == null) throw new NullPointerException(); 1483 | mCharacteristics = characteristics; 1484 | return this; 1485 | } 1486 | 1487 | public synchronized ImageSaver buildIfComplete() { 1488 | if (!isComplete()) { 1489 | return null; 1490 | } 1491 | return new ImageSaver(mImage, mFile, mCaptureResult, mCharacteristics, mContext, 1492 | mReader); 1493 | } 1494 | 1495 | public synchronized String getSaveLocation() { 1496 | return (mFile == null) ? "Unknown" : mFile.toString(); 1497 | } 1498 | 1499 | private boolean isComplete() { 1500 | return mImage != null && mFile != null && mCaptureResult != null 1501 | && mCharacteristics != null; 1502 | } 1503 | } 1504 | } 1505 | 1506 | // Utility classes and methods: 1507 | // ********************************************************************************************* 1508 | 1509 | /** 1510 | * Comparator based on area of the given {@link Size} objects. 1511 | */ 1512 | static class CompareSizesByArea implements Comparator { 1513 | 1514 | @Override 1515 | public int compare(Size lhs, Size rhs) { 1516 | // We cast here to ensure the multiplications won't overflow 1517 | return Long.signum((long) lhs.getWidth() * lhs.getHeight() - 1518 | (long) rhs.getWidth() * rhs.getHeight()); 1519 | } 1520 | 1521 | } 1522 | 1523 | /** 1524 | * A dialog fragment for displaying non-recoverable errors; this {@ling Activity} will be 1525 | * finished once the dialog has been acknowledged by the user. 1526 | */ 1527 | public static class ErrorDialog extends DialogFragment { 1528 | 1529 | private String mErrorMessage; 1530 | 1531 | public ErrorDialog() { 1532 | mErrorMessage = "Unknown error occurred!"; 1533 | } 1534 | 1535 | // Build a dialog with a custom message (Fragments require default constructor). 1536 | public static ErrorDialog buildErrorDialog(String errorMessage) { 1537 | ErrorDialog dialog = new ErrorDialog(); 1538 | dialog.mErrorMessage = errorMessage; 1539 | return dialog; 1540 | } 1541 | 1542 | @Override 1543 | public Dialog onCreateDialog(Bundle savedInstanceState) { 1544 | final Activity activity = getActivity(); 1545 | return new AlertDialog.Builder(activity) 1546 | .setMessage(mErrorMessage) 1547 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1548 | @Override 1549 | public void onClick(DialogInterface dialogInterface, int i) { 1550 | activity.finish(); 1551 | } 1552 | }) 1553 | .create(); 1554 | } 1555 | } 1556 | 1557 | /** 1558 | * A wrapper for an {@link AutoCloseable} object that implements reference counting to allow 1559 | * for resource management. 1560 | */ 1561 | public static class RefCountedAutoCloseable implements AutoCloseable { 1562 | private T mObject; 1563 | private long mRefCount = 0; 1564 | 1565 | /** 1566 | * Wrap the given object. 1567 | * 1568 | * @param object an object to wrap. 1569 | */ 1570 | public RefCountedAutoCloseable(T object) { 1571 | if (object == null) throw new NullPointerException(); 1572 | mObject = object; 1573 | } 1574 | 1575 | /** 1576 | * Increment the reference count and return the wrapped object. 1577 | * 1578 | * @return the wrapped object, or null if the object has been released. 1579 | */ 1580 | public synchronized T getAndRetain() { 1581 | if (mRefCount < 0) { 1582 | return null; 1583 | } 1584 | mRefCount++; 1585 | return mObject; 1586 | } 1587 | 1588 | /** 1589 | * Return the wrapped object. 1590 | * 1591 | * @return the wrapped object, or null if the object has been released. 1592 | */ 1593 | public synchronized T get() { 1594 | return mObject; 1595 | } 1596 | 1597 | /** 1598 | * Decrement the reference count and release the wrapped object if there are no other 1599 | * users retaining this object. 1600 | */ 1601 | @Override 1602 | public synchronized void close() { 1603 | if (mRefCount >= 0) { 1604 | mRefCount--; 1605 | if (mRefCount < 0) { 1606 | try { 1607 | mObject.close(); 1608 | } catch (Exception e) { 1609 | throw new RuntimeException(e); 1610 | } finally { 1611 | mObject = null; 1612 | } 1613 | } 1614 | } 1615 | } 1616 | } 1617 | 1618 | /** 1619 | * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that 1620 | * is at least as large as the respective texture view size, and that is at most as large as the 1621 | * respective max size, and whose aspect ratio matches with the specified value. If such size 1622 | * doesn't exist, choose the largest one that is at most as large as the respective max size, 1623 | * and whose aspect ratio matches with the specified value. 1624 | * 1625 | * @param choices The list of sizes that the camera supports for the intended output 1626 | * class 1627 | * @param textureViewWidth The width of the texture view relative to sensor coordinate 1628 | * @param textureViewHeight The height of the texture view relative to sensor coordinate 1629 | * @param maxWidth The maximum width that can be chosen 1630 | * @param maxHeight The maximum height that can be chosen 1631 | * @param aspectRatio The aspect ratio 1632 | * @return The optimal {@code Size}, or an arbitrary one if none were big enough 1633 | */ 1634 | private static Size chooseOptimalSize(Size[] choices, int textureViewWidth, 1635 | int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) { 1636 | // Collect the supported resolutions that are at least as big as the preview Surface 1637 | List bigEnough = new ArrayList<>(); 1638 | // Collect the supported resolutions that are smaller than the preview Surface 1639 | List notBigEnough = new ArrayList<>(); 1640 | int w = aspectRatio.getWidth(); 1641 | int h = aspectRatio.getHeight(); 1642 | for (Size option : choices) { 1643 | if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight && 1644 | option.getHeight() == option.getWidth() * h / w) { 1645 | if (option.getWidth() >= textureViewWidth && 1646 | option.getHeight() >= textureViewHeight) { 1647 | bigEnough.add(option); 1648 | } else { 1649 | notBigEnough.add(option); 1650 | } 1651 | } 1652 | } 1653 | 1654 | // Pick the smallest of those big enough. If there is no one big enough, pick the 1655 | // largest of those not big enough. 1656 | if (bigEnough.size() > 0) { 1657 | return Collections.min(bigEnough, new CompareSizesByArea()); 1658 | } else if (notBigEnough.size() > 0) { 1659 | return Collections.max(notBigEnough, new CompareSizesByArea()); 1660 | } else { 1661 | Log.e(TAG, "Couldn't find any suitable preview size"); 1662 | return choices[0]; 1663 | } 1664 | } 1665 | 1666 | /** 1667 | * Generate a string containing a formatted timestamp with the current date and time. 1668 | * 1669 | * @return a {@link String} representing a time. 1670 | */ 1671 | private static String generateTimestamp() { 1672 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SSS", Locale.US); 1673 | return sdf.format(new Date()); 1674 | } 1675 | 1676 | /** 1677 | * Cleanup the given {@link OutputStream}. 1678 | * 1679 | * @param outputStream the stream to close. 1680 | */ 1681 | private static void closeOutput(OutputStream outputStream) { 1682 | if (null != outputStream) { 1683 | try { 1684 | outputStream.close(); 1685 | } catch (IOException e) { 1686 | e.printStackTrace(); 1687 | } 1688 | } 1689 | } 1690 | 1691 | /** 1692 | * Return true if the given array contains the given integer. 1693 | * 1694 | * @param modes array to check. 1695 | * @param mode integer to get for. 1696 | * @return true if the array contains the given integer, otherwise false. 1697 | */ 1698 | private static boolean contains(int[] modes, int mode) { 1699 | if (modes == null) { 1700 | return false; 1701 | } 1702 | for (int i : modes) { 1703 | if (i == mode) { 1704 | return true; 1705 | } 1706 | } 1707 | return false; 1708 | } 1709 | 1710 | /** 1711 | * Return true if the two given {@link Size}s have the same aspect ratio. 1712 | * 1713 | * @param a first {@link Size} to compare. 1714 | * @param b second {@link Size} to compare. 1715 | * @return true if the sizes have the same aspect ratio, otherwise false. 1716 | */ 1717 | private static boolean checkAspectsEqual(Size a, Size b) { 1718 | double aAspect = a.getWidth() / (double) a.getHeight(); 1719 | double bAspect = b.getWidth() / (double) b.getHeight(); 1720 | return Math.abs(aAspect - bAspect) <= ASPECT_RATIO_TOLERANCE; 1721 | } 1722 | 1723 | /** 1724 | * Rotation need to transform from the camera sensor orientation to the device's current 1725 | * orientation. 1726 | * 1727 | * @param c the {@link CameraCharacteristics} to query for the camera sensor 1728 | * orientation. 1729 | * @param deviceOrientation the current device orientation relative to the native device 1730 | * orientation. 1731 | * @return the total rotation from the sensor orientation to the current device orientation. 1732 | */ 1733 | private static int sensorToDeviceRotation(CameraCharacteristics c, int deviceOrientation) { 1734 | int sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION); 1735 | 1736 | // Get device orientation in degrees 1737 | deviceOrientation = ORIENTATIONS.get(deviceOrientation); 1738 | 1739 | // Reverse device orientation for front-facing cameras 1740 | if (c.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) { 1741 | deviceOrientation = -deviceOrientation; 1742 | } 1743 | 1744 | // Calculate desired JPEG orientation relative to camera orientation to make 1745 | // the image upright relative to the device orientation 1746 | return (sensorOrientation - deviceOrientation + 360) % 360; 1747 | } 1748 | 1749 | /** 1750 | * Shows a {@link Toast} on the UI thread. 1751 | * 1752 | * @param text The message to show. 1753 | */ 1754 | private void showToast(String text) { 1755 | // We show a Toast by sending request message to mMessageHandler. This makes sure that the 1756 | // Toast is shown on the UI thread. 1757 | Message message = Message.obtain(); 1758 | message.obj = text; 1759 | mMessageHandler.sendMessage(message); 1760 | } 1761 | 1762 | /** 1763 | * If the given request has been completed, remove it from the queue of active requests and 1764 | * send an {@link ImageSaver} with the results from this request to a background thread to 1765 | * save a file. 1766 | *

1767 | * Call this only with {@link #mCameraStateLock} held. 1768 | * 1769 | * @param requestId the ID of the {@link CaptureRequest} to handle. 1770 | * @param builder the {@link ImageSaver.ImageSaverBuilder} for this request. 1771 | * @param queue the queue to remove this request from, if completed. 1772 | */ 1773 | private void handleCompletionLocked(int requestId, ImageSaver.ImageSaverBuilder builder, 1774 | TreeMap queue) { 1775 | if (builder == null) return; 1776 | ImageSaver saver = builder.buildIfComplete(); 1777 | if (saver != null) { 1778 | queue.remove(requestId); 1779 | AsyncTask.THREAD_POOL_EXECUTOR.execute(saver); 1780 | } 1781 | } 1782 | 1783 | /** 1784 | * Check if we are using a device that only supports the LEGACY hardware level. 1785 | *

1786 | * Call this only with {@link #mCameraStateLock} held. 1787 | * 1788 | * @return true if this is a legacy device. 1789 | */ 1790 | private boolean isLegacyLocked() { 1791 | return mCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) == 1792 | CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; 1793 | } 1794 | 1795 | /** 1796 | * Start the timer for the pre-capture sequence. 1797 | *

1798 | * Call this only with {@link #mCameraStateLock} held. 1799 | */ 1800 | private void startTimerLocked() { 1801 | mCaptureTimer = SystemClock.elapsedRealtime(); 1802 | } 1803 | 1804 | /** 1805 | * Check if the timer for the pre-capture sequence has been hit. 1806 | *

1807 | * Call this only with {@link #mCameraStateLock} held. 1808 | * 1809 | * @return true if the timeout occurred. 1810 | */ 1811 | private boolean hitTimeoutLocked() { 1812 | return (SystemClock.elapsedRealtime() - mCaptureTimer) > PRECAPTURE_TIMEOUT_MS; 1813 | } 1814 | 1815 | /** 1816 | * A dialog that explains about the necessary permissions. 1817 | */ 1818 | public static class PermissionConfirmationDialog extends DialogFragment { 1819 | 1820 | public static PermissionConfirmationDialog newInstance() { 1821 | return new PermissionConfirmationDialog(); 1822 | } 1823 | 1824 | @Override 1825 | public Dialog onCreateDialog(Bundle savedInstanceState) { 1826 | final Fragment parent = getParentFragment(); 1827 | return new AlertDialog.Builder(getActivity()) 1828 | .setMessage(R.string.request_permission) 1829 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1830 | @Override 1831 | public void onClick(DialogInterface dialog, int which) { 1832 | FragmentCompat.requestPermissions(parent, CAMERA_PERMISSIONS, 1833 | REQUEST_CAMERA_PERMISSIONS); 1834 | } 1835 | }) 1836 | .setNegativeButton(android.R.string.cancel, 1837 | new DialogInterface.OnClickListener() { 1838 | @Override 1839 | public void onClick(DialogInterface dialog, int which) { 1840 | getActivity().finish(); 1841 | } 1842 | }) 1843 | .create(); 1844 | } 1845 | 1846 | } 1847 | 1848 | } 1849 | -------------------------------------------------------------------------------- /Application/src/main/java/com/example/android/camera2raw/CameraActivity.java: -------------------------------------------------------------------------------- 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.os.Bundle; 21 | 22 | /** 23 | * Activity displaying a fragment that implements RAW photo captures. 24 | */ 25 | public class CameraActivity extends Activity { 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setContentView(R.layout.activity_camera); 31 | if (null == savedInstanceState) { 32 | getFragmentManager().beginTransaction() 33 | .replace(R.id.container, Camera2RawFragment.newInstance()) 34 | .commit(); 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Application/src/main/res/drawable-hdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/8ef76bbc26ff44e5dd7584efbc763d58803bf45c/Application/src/main/res/drawable-hdpi/ic_action_info.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/8ef76bbc26ff44e5dd7584efbc763d58803bf45c/Application/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-hdpi/tile.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/8ef76bbc26ff44e5dd7584efbc763d58803bf45c/Application/src/main/res/drawable-hdpi/tile.9.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-mdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/8ef76bbc26ff44e5dd7584efbc763d58803bf45c/Application/src/main/res/drawable-mdpi/ic_action_info.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/8ef76bbc26ff44e5dd7584efbc763d58803bf45c/Application/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-xhdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/8ef76bbc26ff44e5dd7584efbc763d58803bf45c/Application/src/main/res/drawable-xhdpi/ic_action_info.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/8ef76bbc26ff44e5dd7584efbc763d58803bf45c/Application/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-xxhdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/8ef76bbc26ff44e5dd7584efbc763d58803bf45c/Application/src/main/res/drawable-xxhdpi/ic_action_info.png -------------------------------------------------------------------------------- /Application/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/8ef76bbc26ff44e5dd7584efbc763d58803bf45c/Application/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Application/src/main/res/layout-land/fragment_camera2_basic.xml: -------------------------------------------------------------------------------- 1 | 16 | 19 | 20 | 27 | 28 | 38 | 39 |