1 /* 2 * Copyright 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.camera2basic 18 19 import android.Manifest 20 import android.app.AlertDialog 21 import android.content.Context 22 import android.content.pm.PackageManager 23 import android.content.res.Configuration 24 import android.graphics.ImageFormat 25 import android.graphics.Matrix 26 import android.graphics.Point 27 import android.graphics.RectF 28 import android.graphics.SurfaceTexture 29 import android.hardware.camera2.CameraAccessException 30 import android.hardware.camera2.CameraCaptureSession 31 import android.hardware.camera2.CameraCharacteristics 32 import android.hardware.camera2.CameraDevice 33 import android.hardware.camera2.CameraManager 34 import android.hardware.camera2.CameraMetadata 35 import android.hardware.camera2.CaptureRequest 36 import android.hardware.camera2.CaptureResult 37 import android.hardware.camera2.TotalCaptureResult 38 import android.media.ImageReader 39 import android.os.Bundle 40 import android.os.Handler 41 import android.os.HandlerThread 42 import android.support.v4.app.ActivityCompat 43 import android.support.v4.app.Fragment 44 import android.support.v4.content.ContextCompat 45 import android.util.Log 46 import android.util.Size 47 import android.util.SparseIntArray 48 import android.view.LayoutInflater 49 import android.view.Surface 50 import android.view.TextureView 51 import android.view.View 52 import android.view.ViewGroup 53 import java.io.File 54 import java.util.Arrays 55 import java.util.Collections 56 import java.util.concurrent.Semaphore 57 import java.util.concurrent.TimeUnit 58 import kotlin.collections.ArrayList 59 60 class Camera2BasicFragment : Fragment(), View.OnClickListener, 61 ActivityCompat.OnRequestPermissionsResultCallback { 62 63 /** 64 * [TextureView.SurfaceTextureListener] handles several lifecycle events on a 65 * [TextureView]. 66 */ 67 private val surfaceTextureListener = object : TextureView.SurfaceTextureListener { 68 onSurfaceTextureAvailablenull69 override fun onSurfaceTextureAvailable(texture: SurfaceTexture, width: Int, height: Int) { 70 openCamera(width, height) 71 } 72 onSurfaceTextureSizeChangednull73 override fun onSurfaceTextureSizeChanged(texture: SurfaceTexture, width: Int, height: Int) { 74 configureTransform(width, height) 75 } 76 onSurfaceTextureDestroyednull77 override fun onSurfaceTextureDestroyed(texture: SurfaceTexture) = true 78 79 override fun onSurfaceTextureUpdated(texture: SurfaceTexture) = Unit 80 81 } 82 83 /** 84 * ID of the current [CameraDevice]. 85 */ 86 private lateinit var cameraId: String 87 88 /** 89 * An [AutoFitTextureView] for camera preview. 90 */ 91 private lateinit var textureView: AutoFitTextureView 92 93 /** 94 * A [CameraCaptureSession] for camera preview. 95 */ 96 private var captureSession: CameraCaptureSession? = null 97 98 /** 99 * A reference to the opened [CameraDevice]. 100 */ 101 private var cameraDevice: CameraDevice? = null 102 103 /** 104 * The [android.util.Size] of camera preview. 105 */ 106 private lateinit var previewSize: Size 107 108 /** 109 * [CameraDevice.StateCallback] is called when [CameraDevice] changes its state. 110 */ 111 private val stateCallback = object : CameraDevice.StateCallback() { 112 113 override fun onOpened(cameraDevice: CameraDevice) { 114 cameraOpenCloseLock.release() 115 this@Camera2BasicFragment.cameraDevice = cameraDevice 116 createCameraPreviewSession() 117 } 118 119 override fun onDisconnected(cameraDevice: CameraDevice) { 120 cameraOpenCloseLock.release() 121 cameraDevice.close() 122 this@Camera2BasicFragment.cameraDevice = null 123 } 124 125 override fun onError(cameraDevice: CameraDevice, error: Int) { 126 onDisconnected(cameraDevice) 127 this@Camera2BasicFragment.activity?.finish() 128 } 129 130 } 131 132 /** 133 * An additional thread for running tasks that shouldn't block the UI. 134 */ 135 private var backgroundThread: HandlerThread? = null 136 137 /** 138 * A [Handler] for running tasks in the background. 139 */ 140 private var backgroundHandler: Handler? = null 141 142 /** 143 * An [ImageReader] that handles still image capture. 144 */ 145 private var imageReader: ImageReader? = null 146 147 /** 148 * This is the output file for our picture. 149 */ 150 private lateinit var file: File 151 152 /** 153 * This a callback object for the [ImageReader]. "onImageAvailable" will be called when a 154 * still image is ready to be saved. 155 */ <lambda>null156 private val onImageAvailableListener = ImageReader.OnImageAvailableListener { 157 backgroundHandler?.post(ImageSaver(it.acquireNextImage(), file)) 158 } 159 160 /** 161 * [CaptureRequest.Builder] for the camera preview 162 */ 163 private lateinit var previewRequestBuilder: CaptureRequest.Builder 164 165 /** 166 * [CaptureRequest] generated by [.previewRequestBuilder] 167 */ 168 private lateinit var previewRequest: CaptureRequest 169 170 /** 171 * The current state of camera state for taking pictures. 172 * 173 * @see .captureCallback 174 */ 175 private var state = STATE_PREVIEW 176 177 /** 178 * A [Semaphore] to prevent the app from exiting before closing the camera. 179 */ 180 private val cameraOpenCloseLock = Semaphore(1) 181 182 /** 183 * Whether the current camera device supports Flash or not. 184 */ 185 private var flashSupported = false 186 187 /** 188 * Orientation of the camera sensor 189 */ 190 private var sensorOrientation = 0 191 192 /** 193 * A [CameraCaptureSession.CaptureCallback] that handles events related to JPEG capture. 194 */ 195 private val captureCallback = object : CameraCaptureSession.CaptureCallback() { 196 processnull197 private fun process(result: CaptureResult) { 198 when (state) { 199 STATE_PREVIEW -> Unit // Do nothing when the camera preview is working normally. 200 STATE_WAITING_LOCK -> capturePicture(result) 201 STATE_WAITING_PRECAPTURE -> { 202 // CONTROL_AE_STATE can be null on some devices 203 val aeState = result.get(CaptureResult.CONTROL_AE_STATE) 204 if (aeState == null || 205 aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || 206 aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { 207 state = STATE_WAITING_NON_PRECAPTURE 208 } 209 } 210 STATE_WAITING_NON_PRECAPTURE -> { 211 // CONTROL_AE_STATE can be null on some devices 212 val aeState = result.get(CaptureResult.CONTROL_AE_STATE) 213 if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { 214 state = STATE_PICTURE_TAKEN 215 captureStillPicture() 216 } 217 } 218 } 219 } 220 capturePicturenull221 private fun capturePicture(result: CaptureResult) { 222 val afState = result.get(CaptureResult.CONTROL_AF_STATE) 223 if (afState == null) { 224 captureStillPicture() 225 } else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED 226 || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { 227 // CONTROL_AE_STATE can be null on some devices 228 val aeState = result.get(CaptureResult.CONTROL_AE_STATE) 229 if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { 230 state = STATE_PICTURE_TAKEN 231 captureStillPicture() 232 } else { 233 runPrecaptureSequence() 234 } 235 } 236 } 237 onCaptureProgressednull238 override fun onCaptureProgressed(session: CameraCaptureSession, 239 request: CaptureRequest, 240 partialResult: CaptureResult) { 241 process(partialResult) 242 } 243 onCaptureCompletednull244 override fun onCaptureCompleted(session: CameraCaptureSession, 245 request: CaptureRequest, 246 result: TotalCaptureResult) { 247 process(result) 248 } 249 250 } 251 onCreateViewnull252 override fun onCreateView(inflater: LayoutInflater, 253 container: ViewGroup?, 254 savedInstanceState: Bundle? 255 ): View? = inflater.inflate(R.layout.fragment_camera2_basic, container, false) 256 257 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 258 view.findViewById<View>(R.id.picture).setOnClickListener(this) 259 view.findViewById<View>(R.id.info).setOnClickListener(this) 260 textureView = view.findViewById(R.id.texture) 261 } 262 onActivityCreatednull263 override fun onActivityCreated(savedInstanceState: Bundle?) { 264 super.onActivityCreated(savedInstanceState) 265 file = File(activity.getExternalFilesDir(null), PIC_FILE_NAME) 266 } 267 onResumenull268 override fun onResume() { 269 super.onResume() 270 startBackgroundThread() 271 272 // When the screen is turned off and turned back on, the SurfaceTexture is already 273 // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open 274 // a camera and start preview from here (otherwise, we wait until the surface is ready in 275 // the SurfaceTextureListener). 276 if (textureView.isAvailable) { 277 openCamera(textureView.width, textureView.height) 278 } else { 279 textureView.surfaceTextureListener = surfaceTextureListener 280 } 281 } 282 onPausenull283 override fun onPause() { 284 closeCamera() 285 stopBackgroundThread() 286 super.onPause() 287 } 288 requestCameraPermissionnull289 private fun requestCameraPermission() { 290 if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) { 291 ConfirmationDialog().show(childFragmentManager, FRAGMENT_DIALOG) 292 } else { 293 requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION) 294 } 295 } 296 onRequestPermissionsResultnull297 override fun onRequestPermissionsResult(requestCode: Int, 298 permissions: Array<String>, 299 grantResults: IntArray) { 300 if (requestCode == REQUEST_CAMERA_PERMISSION) { 301 if (grantResults.size != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) { 302 ErrorDialog.newInstance(getString(R.string.request_permission)) 303 .show(childFragmentManager, FRAGMENT_DIALOG) 304 } 305 } else { 306 super.onRequestPermissionsResult(requestCode, permissions, grantResults) 307 } 308 } 309 310 /** 311 * Sets up member variables related to camera. 312 * 313 * @param width The width of available size for camera preview 314 * @param height The height of available size for camera preview 315 */ setUpCameraOutputsnull316 private fun setUpCameraOutputs(width: Int, height: Int) { 317 val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager 318 try { 319 for (cameraId in manager.cameraIdList) { 320 val characteristics = manager.getCameraCharacteristics(cameraId) 321 322 // We don't use a front facing camera in this sample. 323 val cameraDirection = characteristics.get(CameraCharacteristics.LENS_FACING) 324 if (cameraDirection != null && 325 cameraDirection == CameraCharacteristics.LENS_FACING_FRONT) { 326 continue 327 } 328 329 val map = characteristics.get( 330 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue 331 332 // For still image captures, we use the largest available size. 333 val largest = Collections.max( 334 Arrays.asList(*map.getOutputSizes(ImageFormat.JPEG)), 335 CompareSizesByArea()) 336 imageReader = ImageReader.newInstance(largest.width, largest.height, 337 ImageFormat.JPEG, /*maxImages*/ 2).apply { 338 setOnImageAvailableListener(onImageAvailableListener, backgroundHandler) 339 } 340 341 // Find out if we need to swap dimension to get the preview size relative to sensor 342 // coordinate. 343 val displayRotation = activity.windowManager.defaultDisplay.rotation 344 345 sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) 346 val swappedDimensions = areDimensionsSwapped(displayRotation) 347 348 val displaySize = Point() 349 activity.windowManager.defaultDisplay.getSize(displaySize) 350 val rotatedPreviewWidth = if (swappedDimensions) height else width 351 val rotatedPreviewHeight = if (swappedDimensions) width else height 352 var maxPreviewWidth = if (swappedDimensions) displaySize.y else displaySize.x 353 var maxPreviewHeight = if (swappedDimensions) displaySize.x else displaySize.y 354 355 if (maxPreviewWidth > MAX_PREVIEW_WIDTH) maxPreviewWidth = MAX_PREVIEW_WIDTH 356 if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) maxPreviewHeight = MAX_PREVIEW_HEIGHT 357 358 // Danger, W.R.! Attempting to use too large a preview size could exceed the camera 359 // bus' bandwidth limitation, resulting in gorgeous previews but the storage of 360 // garbage capture data. 361 previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture::class.java), 362 rotatedPreviewWidth, rotatedPreviewHeight, 363 maxPreviewWidth, maxPreviewHeight, 364 largest) 365 366 // We fit the aspect ratio of TextureView to the size of preview we picked. 367 if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { 368 textureView.setAspectRatio(previewSize.width, previewSize.height) 369 } else { 370 textureView.setAspectRatio(previewSize.height, previewSize.width) 371 } 372 373 // Check if the flash is supported. 374 flashSupported = 375 characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) == true 376 377 this.cameraId = cameraId 378 379 // We've found a viable camera and finished setting up member variables, 380 // so we don't need to iterate through other available cameras. 381 return 382 } 383 } catch (e: CameraAccessException) { 384 Log.e(TAG, e.toString()) 385 } catch (e: NullPointerException) { 386 // Currently an NPE is thrown when the Camera2API is used but not supported on the 387 // device this code runs. 388 ErrorDialog.newInstance(getString(R.string.camera_error)) 389 .show(childFragmentManager, FRAGMENT_DIALOG) 390 } 391 392 } 393 394 /** 395 * Determines if the dimensions are swapped given the phone's current rotation. 396 * 397 * @param displayRotation The current rotation of the display 398 * 399 * @return true if the dimensions are swapped, false otherwise. 400 */ areDimensionsSwappednull401 private fun areDimensionsSwapped(displayRotation: Int): Boolean { 402 var swappedDimensions = false 403 when (displayRotation) { 404 Surface.ROTATION_0, Surface.ROTATION_180 -> { 405 if (sensorOrientation == 90 || sensorOrientation == 270) { 406 swappedDimensions = true 407 } 408 } 409 Surface.ROTATION_90, Surface.ROTATION_270 -> { 410 if (sensorOrientation == 0 || sensorOrientation == 180) { 411 swappedDimensions = true 412 } 413 } 414 else -> { 415 Log.e(TAG, "Display rotation is invalid: $displayRotation") 416 } 417 } 418 return swappedDimensions 419 } 420 421 /** 422 * Opens the camera specified by [Camera2BasicFragment.cameraId]. 423 */ openCameranull424 private fun openCamera(width: Int, height: Int) { 425 val permission = ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) 426 if (permission != PackageManager.PERMISSION_GRANTED) { 427 requestCameraPermission() 428 return 429 } 430 setUpCameraOutputs(width, height) 431 configureTransform(width, height) 432 val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager 433 try { 434 // Wait for camera to open - 2.5 seconds is sufficient 435 if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { 436 throw RuntimeException("Time out waiting to lock camera opening.") 437 } 438 manager.openCamera(cameraId, stateCallback, backgroundHandler) 439 } catch (e: CameraAccessException) { 440 Log.e(TAG, e.toString()) 441 } catch (e: InterruptedException) { 442 throw RuntimeException("Interrupted while trying to lock camera opening.", e) 443 } 444 445 } 446 447 /** 448 * Closes the current [CameraDevice]. 449 */ closeCameranull450 private fun closeCamera() { 451 try { 452 cameraOpenCloseLock.acquire() 453 captureSession?.close() 454 captureSession = null 455 cameraDevice?.close() 456 cameraDevice = null 457 imageReader?.close() 458 imageReader = null 459 } catch (e: InterruptedException) { 460 throw RuntimeException("Interrupted while trying to lock camera closing.", e) 461 } finally { 462 cameraOpenCloseLock.release() 463 } 464 } 465 466 /** 467 * Starts a background thread and its [Handler]. 468 */ startBackgroundThreadnull469 private fun startBackgroundThread() { 470 backgroundThread = HandlerThread("CameraBackground").also { it.start() } 471 backgroundHandler = Handler(backgroundThread?.looper) 472 } 473 474 /** 475 * Stops the background thread and its [Handler]. 476 */ stopBackgroundThreadnull477 private fun stopBackgroundThread() { 478 backgroundThread?.quitSafely() 479 try { 480 backgroundThread?.join() 481 backgroundThread = null 482 backgroundHandler = null 483 } catch (e: InterruptedException) { 484 Log.e(TAG, e.toString()) 485 } 486 487 } 488 489 /** 490 * Creates a new [CameraCaptureSession] for camera preview. 491 */ createCameraPreviewSessionnull492 private fun createCameraPreviewSession() { 493 try { 494 val texture = textureView.surfaceTexture 495 496 // We configure the size of default buffer to be the size of camera preview we want. 497 texture.setDefaultBufferSize(previewSize.width, previewSize.height) 498 499 // This is the output Surface we need to start preview. 500 val surface = Surface(texture) 501 502 // We set up a CaptureRequest.Builder with the output Surface. 503 previewRequestBuilder = cameraDevice!!.createCaptureRequest( 504 CameraDevice.TEMPLATE_PREVIEW 505 ) 506 previewRequestBuilder.addTarget(surface) 507 508 // Here, we create a CameraCaptureSession for camera preview. 509 cameraDevice?.createCaptureSession(Arrays.asList(surface, imageReader?.surface), 510 object : CameraCaptureSession.StateCallback() { 511 512 override fun onConfigured(cameraCaptureSession: CameraCaptureSession) { 513 // The camera is already closed 514 if (cameraDevice == null) return 515 516 // When the session is ready, we start displaying the preview. 517 captureSession = cameraCaptureSession 518 try { 519 // Auto focus should be continuous for camera preview. 520 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, 521 CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) 522 // Flash is automatically enabled when necessary. 523 setAutoFlash(previewRequestBuilder) 524 525 // Finally, we start displaying the camera preview. 526 previewRequest = previewRequestBuilder.build() 527 captureSession?.setRepeatingRequest(previewRequest, 528 captureCallback, backgroundHandler) 529 } catch (e: CameraAccessException) { 530 Log.e(TAG, e.toString()) 531 } 532 533 } 534 535 override fun onConfigureFailed(session: CameraCaptureSession) { 536 activity.showToast("Failed") 537 } 538 }, null) 539 } catch (e: CameraAccessException) { 540 Log.e(TAG, e.toString()) 541 } 542 543 } 544 545 /** 546 * Configures the necessary [android.graphics.Matrix] transformation to `textureView`. 547 * This method should be called after the camera preview size is determined in 548 * setUpCameraOutputs and also the size of `textureView` is fixed. 549 * 550 * @param viewWidth The width of `textureView` 551 * @param viewHeight The height of `textureView` 552 */ configureTransformnull553 private fun configureTransform(viewWidth: Int, viewHeight: Int) { 554 activity ?: return 555 val rotation = activity.windowManager.defaultDisplay.rotation 556 val matrix = Matrix() 557 val viewRect = RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat()) 558 val bufferRect = RectF(0f, 0f, previewSize.height.toFloat(), previewSize.width.toFloat()) 559 val centerX = viewRect.centerX() 560 val centerY = viewRect.centerY() 561 562 if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { 563 bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()) 564 val scale = Math.max( 565 viewHeight.toFloat() / previewSize.height, 566 viewWidth.toFloat() / previewSize.width) 567 with(matrix) { 568 setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL) 569 postScale(scale, scale, centerX, centerY) 570 postRotate((90 * (rotation - 2)).toFloat(), centerX, centerY) 571 } 572 } else if (Surface.ROTATION_180 == rotation) { 573 matrix.postRotate(180f, centerX, centerY) 574 } 575 textureView.setTransform(matrix) 576 } 577 578 /** 579 * Lock the focus as the first step for a still image capture. 580 */ lockFocusnull581 private fun lockFocus() { 582 try { 583 // This is how to tell the camera to lock focus. 584 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 585 CameraMetadata.CONTROL_AF_TRIGGER_START) 586 // Tell #captureCallback to wait for the lock. 587 state = STATE_WAITING_LOCK 588 captureSession?.capture(previewRequestBuilder.build(), captureCallback, 589 backgroundHandler) 590 } catch (e: CameraAccessException) { 591 Log.e(TAG, e.toString()) 592 } 593 594 } 595 596 /** 597 * Run the precapture sequence for capturing a still image. This method should be called when 598 * we get a response in [.captureCallback] from [.lockFocus]. 599 */ runPrecaptureSequencenull600 private fun runPrecaptureSequence() { 601 try { 602 // This is how to tell the camera to trigger. 603 previewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, 604 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START) 605 // Tell #captureCallback to wait for the precapture sequence to be set. 606 state = STATE_WAITING_PRECAPTURE 607 captureSession?.capture(previewRequestBuilder.build(), captureCallback, 608 backgroundHandler) 609 } catch (e: CameraAccessException) { 610 Log.e(TAG, e.toString()) 611 } 612 613 } 614 615 /** 616 * Capture a still picture. This method should be called when we get a response in 617 * [.captureCallback] from both [.lockFocus]. 618 */ captureStillPicturenull619 private fun captureStillPicture() { 620 try { 621 if (activity == null || cameraDevice == null) return 622 val rotation = activity.windowManager.defaultDisplay.rotation 623 624 // This is the CaptureRequest.Builder that we use to take a picture. 625 val captureBuilder = cameraDevice?.createCaptureRequest( 626 CameraDevice.TEMPLATE_STILL_CAPTURE)?.apply { 627 addTarget(imageReader?.surface) 628 629 // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) 630 // We have to take that into account and rotate JPEG properly. 631 // For devices with orientation of 90, we return our mapping from ORIENTATIONS. 632 // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. 633 set(CaptureRequest.JPEG_ORIENTATION, 634 (ORIENTATIONS.get(rotation) + sensorOrientation + 270) % 360) 635 636 // Use the same AE and AF modes as the preview. 637 set(CaptureRequest.CONTROL_AF_MODE, 638 CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) 639 }?.also { setAutoFlash(it) } 640 641 val captureCallback = object : CameraCaptureSession.CaptureCallback() { 642 643 override fun onCaptureCompleted(session: CameraCaptureSession, 644 request: CaptureRequest, 645 result: TotalCaptureResult) { 646 activity.showToast("Saved: $file") 647 Log.d(TAG, file.toString()) 648 unlockFocus() 649 } 650 } 651 652 captureSession?.apply { 653 stopRepeating() 654 abortCaptures() 655 capture(captureBuilder?.build(), captureCallback, null) 656 } 657 } catch (e: CameraAccessException) { 658 Log.e(TAG, e.toString()) 659 } 660 661 } 662 663 /** 664 * Unlock the focus. This method should be called when still image capture sequence is 665 * finished. 666 */ unlockFocusnull667 private fun unlockFocus() { 668 try { 669 // Reset the auto-focus trigger 670 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 671 CameraMetadata.CONTROL_AF_TRIGGER_CANCEL) 672 setAutoFlash(previewRequestBuilder) 673 captureSession?.capture(previewRequestBuilder.build(), captureCallback, 674 backgroundHandler) 675 // After this, the camera will go back to the normal state of preview. 676 state = STATE_PREVIEW 677 captureSession?.setRepeatingRequest(previewRequest, captureCallback, 678 backgroundHandler) 679 } catch (e: CameraAccessException) { 680 Log.e(TAG, e.toString()) 681 } 682 683 } 684 onClicknull685 override fun onClick(view: View) { 686 when (view.id) { 687 R.id.picture -> lockFocus() 688 R.id.info -> { 689 if (activity != null) { 690 AlertDialog.Builder(activity) 691 .setMessage(R.string.intro_message) 692 .setPositiveButton(android.R.string.ok, null) 693 .show() 694 } 695 } 696 } 697 } 698 setAutoFlashnull699 private fun setAutoFlash(requestBuilder: CaptureRequest.Builder) { 700 if (flashSupported) { 701 requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, 702 CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH) 703 } 704 } 705 706 companion object { 707 708 /** 709 * Conversion from screen rotation to JPEG orientation. 710 */ 711 private val ORIENTATIONS = SparseIntArray() 712 private val FRAGMENT_DIALOG = "dialog" 713 714 init { 715 ORIENTATIONS.append(Surface.ROTATION_0, 90) 716 ORIENTATIONS.append(Surface.ROTATION_90, 0) 717 ORIENTATIONS.append(Surface.ROTATION_180, 270) 718 ORIENTATIONS.append(Surface.ROTATION_270, 180) 719 } 720 721 /** 722 * Tag for the [Log]. 723 */ 724 private val TAG = "Camera2BasicFragment" 725 726 /** 727 * Camera state: Showing camera preview. 728 */ 729 private val STATE_PREVIEW = 0 730 731 /** 732 * Camera state: Waiting for the focus to be locked. 733 */ 734 private val STATE_WAITING_LOCK = 1 735 736 /** 737 * Camera state: Waiting for the exposure to be precapture state. 738 */ 739 private val STATE_WAITING_PRECAPTURE = 2 740 741 /** 742 * Camera state: Waiting for the exposure state to be something other than precapture. 743 */ 744 private val STATE_WAITING_NON_PRECAPTURE = 3 745 746 /** 747 * Camera state: Picture was taken. 748 */ 749 private val STATE_PICTURE_TAKEN = 4 750 751 /** 752 * Max preview width that is guaranteed by Camera2 API 753 */ 754 private val MAX_PREVIEW_WIDTH = 1920 755 756 /** 757 * Max preview height that is guaranteed by Camera2 API 758 */ 759 private val MAX_PREVIEW_HEIGHT = 1080 760 761 /** 762 * Given `choices` of `Size`s supported by a camera, choose the smallest one that 763 * is at least as large as the respective texture view size, and that is at most as large as 764 * the respective max size, and whose aspect ratio matches with the specified value. If such 765 * size doesn't exist, choose the largest one that is at most as large as the respective max 766 * size, and whose aspect ratio matches with the specified value. 767 * 768 * @param choices The list of sizes that the camera supports for the intended 769 * output class 770 * @param textureViewWidth The width of the texture view relative to sensor coordinate 771 * @param textureViewHeight The height of the texture view relative to sensor coordinate 772 * @param maxWidth The maximum width that can be chosen 773 * @param maxHeight The maximum height that can be chosen 774 * @param aspectRatio The aspect ratio 775 * @return The optimal `Size`, or an arbitrary one if none were big enough 776 */ chooseOptimalSizenull777 @JvmStatic private fun chooseOptimalSize( 778 choices: Array<Size>, 779 textureViewWidth: Int, 780 textureViewHeight: Int, 781 maxWidth: Int, 782 maxHeight: Int, 783 aspectRatio: Size 784 ): Size { 785 786 // Collect the supported resolutions that are at least as big as the preview Surface 787 val bigEnough = ArrayList<Size>() 788 // Collect the supported resolutions that are smaller than the preview Surface 789 val notBigEnough = ArrayList<Size>() 790 val w = aspectRatio.width 791 val h = aspectRatio.height 792 for (option in choices) { 793 if (option.width <= maxWidth && option.height <= maxHeight && 794 option.height == option.width * h / w) { 795 if (option.width >= textureViewWidth && option.height >= textureViewHeight) { 796 bigEnough.add(option) 797 } else { 798 notBigEnough.add(option) 799 } 800 } 801 } 802 803 // Pick the smallest of those big enough. If there is no one big enough, pick the 804 // largest of those not big enough. 805 if (bigEnough.size > 0) { 806 return Collections.min(bigEnough, CompareSizesByArea()) 807 } else if (notBigEnough.size > 0) { 808 return Collections.max(notBigEnough, CompareSizesByArea()) 809 } else { 810 Log.e(TAG, "Couldn't find any suitable preview size") 811 return choices[0] 812 } 813 } 814 newInstancenull815 @JvmStatic fun newInstance(): Camera2BasicFragment = Camera2BasicFragment() 816 } 817 } 818