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