blob: c55df1b95ce97037536548ba6def8c36a6d98995 [file] [log] [blame]
/*
* Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.camera2basic
import android.Manifest
import android.app.AlertDialog
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.ImageFormat
import android.graphics.Matrix
import android.graphics.Point
import android.graphics.RectF
import android.graphics.SurfaceTexture
import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraCaptureSession
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CameraManager
import android.hardware.camera2.CameraMetadata
import android.hardware.camera2.CaptureRequest
import android.hardware.camera2.CaptureResult
import android.hardware.camera2.TotalCaptureResult
import android.media.ImageReader
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.support.v4.app.ActivityCompat
import android.support.v4.app.Fragment
import android.support.v4.content.ContextCompat
import android.util.Log
import android.util.Size
import android.util.SparseIntArray
import android.view.LayoutInflater
import android.view.Surface
import android.view.TextureView
import android.view.View
import android.view.ViewGroup
import java.io.File
import java.util.Arrays
import java.util.Collections
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import kotlin.collections.ArrayList
class Camera2BasicFragment : Fragment(), View.OnClickListener,
ActivityCompat.OnRequestPermissionsResultCallback {
/**
* [TextureView.SurfaceTextureListener] handles several lifecycle events on a
* [TextureView].
*/
private val surfaceTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(texture: SurfaceTexture, width: Int, height: Int) {
openCamera(width, height)
}
override fun onSurfaceTextureSizeChanged(texture: SurfaceTexture, width: Int, height: Int) {
configureTransform(width, height)
}
override fun onSurfaceTextureDestroyed(texture: SurfaceTexture) = true
override fun onSurfaceTextureUpdated(texture: SurfaceTexture) = Unit
}
/**
* ID of the current [CameraDevice].
*/
private lateinit var cameraId: String
/**
* An [AutoFitTextureView] for camera preview.
*/
private lateinit var textureView: AutoFitTextureView
/**
* A [CameraCaptureSession] for camera preview.
*/
private var captureSession: CameraCaptureSession? = null
/**
* A reference to the opened [CameraDevice].
*/
private var cameraDevice: CameraDevice? = null
/**
* The [android.util.Size] of camera preview.
*/
private lateinit var previewSize: Size
/**
* [CameraDevice.StateCallback] is called when [CameraDevice] changes its state.
*/
private val stateCallback = object : CameraDevice.StateCallback() {
override fun onOpened(cameraDevice: CameraDevice) {
cameraOpenCloseLock.release()
this@Camera2BasicFragment.cameraDevice = cameraDevice
createCameraPreviewSession()
}
override fun onDisconnected(cameraDevice: CameraDevice) {
cameraOpenCloseLock.release()
cameraDevice.close()
this@Camera2BasicFragment.cameraDevice = null
}
override fun onError(cameraDevice: CameraDevice, error: Int) {
onDisconnected(cameraDevice)
this@Camera2BasicFragment.activity?.finish()
}
}
/**
* An additional thread for running tasks that shouldn't block the UI.
*/
private var backgroundThread: HandlerThread? = null
/**
* A [Handler] for running tasks in the background.
*/
private var backgroundHandler: Handler? = null
/**
* An [ImageReader] that handles still image capture.
*/
private var imageReader: ImageReader? = null
/**
* This is the output file for our picture.
*/
private lateinit var file: File
/**
* This a callback object for the [ImageReader]. "onImageAvailable" will be called when a
* still image is ready to be saved.
*/
private val onImageAvailableListener = ImageReader.OnImageAvailableListener {
backgroundHandler?.post(ImageSaver(it.acquireNextImage(), file))
}
/**
* [CaptureRequest.Builder] for the camera preview
*/
private lateinit var previewRequestBuilder: CaptureRequest.Builder
/**
* [CaptureRequest] generated by [.previewRequestBuilder]
*/
private lateinit var previewRequest: CaptureRequest
/**
* The current state of camera state for taking pictures.
*
* @see .captureCallback
*/
private var state = STATE_PREVIEW
/**
* A [Semaphore] to prevent the app from exiting before closing the camera.
*/
private val cameraOpenCloseLock = Semaphore(1)
/**
* Whether the current camera device supports Flash or not.
*/
private var flashSupported = false
/**
* Orientation of the camera sensor
*/
private var sensorOrientation = 0
/**
* A [CameraCaptureSession.CaptureCallback] that handles events related to JPEG capture.
*/
private val captureCallback = object : CameraCaptureSession.CaptureCallback() {
private fun process(result: CaptureResult) {
when (state) {
STATE_PREVIEW -> Unit // Do nothing when the camera preview is working normally.
STATE_WAITING_LOCK -> capturePicture(result)
STATE_WAITING_PRECAPTURE -> {
// CONTROL_AE_STATE can be null on some devices
val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
if (aeState == null ||
aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
state = STATE_WAITING_NON_PRECAPTURE
}
}
STATE_WAITING_NON_PRECAPTURE -> {
// CONTROL_AE_STATE can be null on some devices
val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
state = STATE_PICTURE_TAKEN
captureStillPicture()
}
}
}
}
private fun capturePicture(result: CaptureResult) {
val afState = result.get(CaptureResult.CONTROL_AF_STATE)
if (afState == null) {
captureStillPicture()
} else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
|| afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
// CONTROL_AE_STATE can be null on some devices
val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
state = STATE_PICTURE_TAKEN
captureStillPicture()
} else {
runPrecaptureSequence()
}
}
}
override fun onCaptureProgressed(session: CameraCaptureSession,
request: CaptureRequest,
partialResult: CaptureResult) {
process(partialResult)
}
override fun onCaptureCompleted(session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult) {
process(result)
}
}
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_camera2_basic, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
view.findViewById<View>(R.id.picture).setOnClickListener(this)
view.findViewById<View>(R.id.info).setOnClickListener(this)
textureView = view.findViewById(R.id.texture)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
file = File(activity.getExternalFilesDir(null), PIC_FILE_NAME)
}
override fun onResume() {
super.onResume()
startBackgroundThread()
// When the screen is turned off and turned back on, the SurfaceTexture is already
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
// a camera and start preview from here (otherwise, we wait until the surface is ready in
// the SurfaceTextureListener).
if (textureView.isAvailable) {
openCamera(textureView.width, textureView.height)
} else {
textureView.surfaceTextureListener = surfaceTextureListener
}
}
override fun onPause() {
closeCamera()
stopBackgroundThread()
super.onPause()
}
private fun requestCameraPermission() {
if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
ConfirmationDialog().show(childFragmentManager, FRAGMENT_DIALOG)
} else {
requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
}
}
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>,
grantResults: IntArray) {
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults.size != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
ErrorDialog.newInstance(getString(R.string.request_permission))
.show(childFragmentManager, FRAGMENT_DIALOG)
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
/**
* Sets up member variables related to camera.
*
* @param width The width of available size for camera preview
* @param height The height of available size for camera preview
*/
private fun setUpCameraOutputs(width: Int, height: Int) {
val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
try {
for (cameraId in manager.cameraIdList) {
val characteristics = manager.getCameraCharacteristics(cameraId)
// We don't use a front facing camera in this sample.
val cameraDirection = characteristics.get(CameraCharacteristics.LENS_FACING)
if (cameraDirection != null &&
cameraDirection == CameraCharacteristics.LENS_FACING_FRONT) {
continue
}
val map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue
// For still image captures, we use the largest available size.
val largest = Collections.max(
Arrays.asList(*map.getOutputSizes(ImageFormat.JPEG)),
CompareSizesByArea())
imageReader = ImageReader.newInstance(largest.width, largest.height,
ImageFormat.JPEG, /*maxImages*/ 2).apply {
setOnImageAvailableListener(onImageAvailableListener, backgroundHandler)
}
// Find out if we need to swap dimension to get the preview size relative to sensor
// coordinate.
val displayRotation = activity.windowManager.defaultDisplay.rotation
sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)
val swappedDimensions = areDimensionsSwapped(displayRotation)
val displaySize = Point()
activity.windowManager.defaultDisplay.getSize(displaySize)
val rotatedPreviewWidth = if (swappedDimensions) height else width
val rotatedPreviewHeight = if (swappedDimensions) width else height
var maxPreviewWidth = if (swappedDimensions) displaySize.y else displaySize.x
var maxPreviewHeight = if (swappedDimensions) displaySize.x else displaySize.y
if (maxPreviewWidth > MAX_PREVIEW_WIDTH) maxPreviewWidth = MAX_PREVIEW_WIDTH
if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) maxPreviewHeight = MAX_PREVIEW_HEIGHT
// Danger, W.R.! Attempting to use too large a preview size could exceed the camera
// bus' bandwidth limitation, resulting in gorgeous previews but the storage of
// garbage capture data.
previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture::class.java),
rotatedPreviewWidth, rotatedPreviewHeight,
maxPreviewWidth, maxPreviewHeight,
largest)
// We fit the aspect ratio of TextureView to the size of preview we picked.
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
textureView.setAspectRatio(previewSize.width, previewSize.height)
} else {
textureView.setAspectRatio(previewSize.height, previewSize.width)
}
// Check if the flash is supported.
flashSupported =
characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) == true
this.cameraId = cameraId
// We've found a viable camera and finished setting up member variables,
// so we don't need to iterate through other available cameras.
return
}
} catch (e: CameraAccessException) {
Log.e(TAG, e.toString())
} catch (e: NullPointerException) {
// Currently an NPE is thrown when the Camera2API is used but not supported on the
// device this code runs.
ErrorDialog.newInstance(getString(R.string.camera_error))
.show(childFragmentManager, FRAGMENT_DIALOG)
}
}
/**
* Determines if the dimensions are swapped given the phone's current rotation.
*
* @param displayRotation The current rotation of the display
*
* @return true if the dimensions are swapped, false otherwise.
*/
private fun areDimensionsSwapped(displayRotation: Int): Boolean {
var swappedDimensions = false
when (displayRotation) {
Surface.ROTATION_0, Surface.ROTATION_180 -> {
if (sensorOrientation == 90 || sensorOrientation == 270) {
swappedDimensions = true
}
}
Surface.ROTATION_90, Surface.ROTATION_270 -> {
if (sensorOrientation == 0 || sensorOrientation == 180) {
swappedDimensions = true
}
}
else -> {
Log.e(TAG, "Display rotation is invalid: $displayRotation")
}
}
return swappedDimensions
}
/**
* Opens the camera specified by [Camera2BasicFragment.cameraId].
*/
private fun openCamera(width: Int, height: Int) {
val permission = ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA)
if (permission != PackageManager.PERMISSION_GRANTED) {
requestCameraPermission()
return
}
setUpCameraOutputs(width, height)
configureTransform(width, height)
val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
try {
// Wait for camera to open - 2.5 seconds is sufficient
if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw RuntimeException("Time out waiting to lock camera opening.")
}
manager.openCamera(cameraId, stateCallback, backgroundHandler)
} catch (e: CameraAccessException) {
Log.e(TAG, e.toString())
} catch (e: InterruptedException) {
throw RuntimeException("Interrupted while trying to lock camera opening.", e)
}
}
/**
* Closes the current [CameraDevice].
*/
private fun closeCamera() {
try {
cameraOpenCloseLock.acquire()
captureSession?.close()
captureSession = null
cameraDevice?.close()
cameraDevice = null
imageReader?.close()
imageReader = null
} catch (e: InterruptedException) {
throw RuntimeException("Interrupted while trying to lock camera closing.", e)
} finally {
cameraOpenCloseLock.release()
}
}
/**
* Starts a background thread and its [Handler].
*/
private fun startBackgroundThread() {
backgroundThread = HandlerThread("CameraBackground").also { it.start() }
backgroundHandler = Handler(backgroundThread?.looper)
}
/**
* Stops the background thread and its [Handler].
*/
private fun stopBackgroundThread() {
backgroundThread?.quitSafely()
try {
backgroundThread?.join()
backgroundThread = null
backgroundHandler = null
} catch (e: InterruptedException) {
Log.e(TAG, e.toString())
}
}
/**
* Creates a new [CameraCaptureSession] for camera preview.
*/
private fun createCameraPreviewSession() {
try {
val texture = textureView.surfaceTexture
// We configure the size of default buffer to be the size of camera preview we want.
texture.setDefaultBufferSize(previewSize.width, previewSize.height)
// This is the output Surface we need to start preview.
val surface = Surface(texture)
// We set up a CaptureRequest.Builder with the output Surface.
previewRequestBuilder = cameraDevice!!.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW
)
previewRequestBuilder.addTarget(surface)
// Here, we create a CameraCaptureSession for camera preview.
cameraDevice?.createCaptureSession(Arrays.asList(surface, imageReader?.surface),
object : CameraCaptureSession.StateCallback() {
override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
// The camera is already closed
if (cameraDevice == null) return
// When the session is ready, we start displaying the preview.
captureSession = cameraCaptureSession
try {
// Auto focus should be continuous for camera preview.
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
// Flash is automatically enabled when necessary.
setAutoFlash(previewRequestBuilder)
// Finally, we start displaying the camera preview.
previewRequest = previewRequestBuilder.build()
captureSession?.setRepeatingRequest(previewRequest,
captureCallback, backgroundHandler)
} catch (e: CameraAccessException) {
Log.e(TAG, e.toString())
}
}
override fun onConfigureFailed(session: CameraCaptureSession) {
activity.showToast("Failed")
}
}, null)
} catch (e: CameraAccessException) {
Log.e(TAG, e.toString())
}
}
/**
* Configures the necessary [android.graphics.Matrix] transformation to `textureView`.
* This method should be called after the camera preview size is determined in
* setUpCameraOutputs and also the size of `textureView` is fixed.
*
* @param viewWidth The width of `textureView`
* @param viewHeight The height of `textureView`
*/
private fun configureTransform(viewWidth: Int, viewHeight: Int) {
activity ?: return
val rotation = activity.windowManager.defaultDisplay.rotation
val matrix = Matrix()
val viewRect = RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat())
val bufferRect = RectF(0f, 0f, previewSize.height.toFloat(), previewSize.width.toFloat())
val centerX = viewRect.centerX()
val centerY = viewRect.centerY()
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY())
val scale = Math.max(
viewHeight.toFloat() / previewSize.height,
viewWidth.toFloat() / previewSize.width)
with(matrix) {
setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL)
postScale(scale, scale, centerX, centerY)
postRotate((90 * (rotation - 2)).toFloat(), centerX, centerY)
}
} else if (Surface.ROTATION_180 == rotation) {
matrix.postRotate(180f, centerX, centerY)
}
textureView.setTransform(matrix)
}
/**
* Lock the focus as the first step for a still image capture.
*/
private fun lockFocus() {
try {
// This is how to tell the camera to lock focus.
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_START)
// Tell #captureCallback to wait for the lock.
state = STATE_WAITING_LOCK
captureSession?.capture(previewRequestBuilder.build(), captureCallback,
backgroundHandler)
} catch (e: CameraAccessException) {
Log.e(TAG, e.toString())
}
}
/**
* Run the precapture sequence for capturing a still image. This method should be called when
* we get a response in [.captureCallback] from [.lockFocus].
*/
private fun runPrecaptureSequence() {
try {
// This is how to tell the camera to trigger.
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START)
// Tell #captureCallback to wait for the precapture sequence to be set.
state = STATE_WAITING_PRECAPTURE
captureSession?.capture(previewRequestBuilder.build(), captureCallback,
backgroundHandler)
} catch (e: CameraAccessException) {
Log.e(TAG, e.toString())
}
}
/**
* Capture a still picture. This method should be called when we get a response in
* [.captureCallback] from both [.lockFocus].
*/
private fun captureStillPicture() {
try {
if (activity == null || cameraDevice == null) return
val rotation = activity.windowManager.defaultDisplay.rotation
// This is the CaptureRequest.Builder that we use to take a picture.
val captureBuilder = cameraDevice?.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE)?.apply {
addTarget(imageReader?.surface)
// Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
// We have to take that into account and rotate JPEG properly.
// For devices with orientation of 90, we return our mapping from ORIENTATIONS.
// For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
set(CaptureRequest.JPEG_ORIENTATION,
(ORIENTATIONS.get(rotation) + sensorOrientation + 270) % 360)
// Use the same AE and AF modes as the preview.
set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
}?.also { setAutoFlash(it) }
val captureCallback = object : CameraCaptureSession.CaptureCallback() {
override fun onCaptureCompleted(session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult) {
activity.showToast("Saved: $file")
Log.d(TAG, file.toString())
unlockFocus()
}
}
captureSession?.apply {
stopRepeating()
abortCaptures()
capture(captureBuilder?.build(), captureCallback, null)
}
} catch (e: CameraAccessException) {
Log.e(TAG, e.toString())
}
}
/**
* Unlock the focus. This method should be called when still image capture sequence is
* finished.
*/
private fun unlockFocus() {
try {
// Reset the auto-focus trigger
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_CANCEL)
setAutoFlash(previewRequestBuilder)
captureSession?.capture(previewRequestBuilder.build(), captureCallback,
backgroundHandler)
// After this, the camera will go back to the normal state of preview.
state = STATE_PREVIEW
captureSession?.setRepeatingRequest(previewRequest, captureCallback,
backgroundHandler)
} catch (e: CameraAccessException) {
Log.e(TAG, e.toString())
}
}
override fun onClick(view: View) {
when (view.id) {
R.id.picture -> lockFocus()
R.id.info -> {
if (activity != null) {
AlertDialog.Builder(activity)
.setMessage(R.string.intro_message)
.setPositiveButton(android.R.string.ok, null)
.show()
}
}
}
}
private fun setAutoFlash(requestBuilder: CaptureRequest.Builder) {
if (flashSupported) {
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
}
}
companion object {
/**
* Conversion from screen rotation to JPEG orientation.
*/
private val ORIENTATIONS = SparseIntArray()
private val FRAGMENT_DIALOG = "dialog"
init {
ORIENTATIONS.append(Surface.ROTATION_0, 90)
ORIENTATIONS.append(Surface.ROTATION_90, 0)
ORIENTATIONS.append(Surface.ROTATION_180, 270)
ORIENTATIONS.append(Surface.ROTATION_270, 180)
}
/**
* Tag for the [Log].
*/
private val TAG = "Camera2BasicFragment"
/**
* Camera state: Showing camera preview.
*/
private val STATE_PREVIEW = 0
/**
* Camera state: Waiting for the focus to be locked.
*/
private val STATE_WAITING_LOCK = 1
/**
* Camera state: Waiting for the exposure to be precapture state.
*/
private val STATE_WAITING_PRECAPTURE = 2
/**
* Camera state: Waiting for the exposure state to be something other than precapture.
*/
private val STATE_WAITING_NON_PRECAPTURE = 3
/**
* Camera state: Picture was taken.
*/
private val STATE_PICTURE_TAKEN = 4
/**
* Max preview width that is guaranteed by Camera2 API
*/
private val MAX_PREVIEW_WIDTH = 1920
/**
* Max preview height that is guaranteed by Camera2 API
*/
private val MAX_PREVIEW_HEIGHT = 1080
/**
* Given `choices` of `Size`s supported by a camera, choose the smallest one that
* is at least as large as the respective texture view size, and that is at most as large as
* the respective max size, and whose aspect ratio matches with the specified value. If such
* size doesn't exist, choose the largest one that is at most as large as the respective max
* size, and whose aspect ratio matches with the specified value.
*
* @param choices The list of sizes that the camera supports for the intended
* output class
* @param textureViewWidth The width of the texture view relative to sensor coordinate
* @param textureViewHeight The height of the texture view relative to sensor coordinate
* @param maxWidth The maximum width that can be chosen
* @param maxHeight The maximum height that can be chosen
* @param aspectRatio The aspect ratio
* @return The optimal `Size`, or an arbitrary one if none were big enough
*/
@JvmStatic private fun chooseOptimalSize(
choices: Array<Size>,
textureViewWidth: Int,
textureViewHeight: Int,
maxWidth: Int,
maxHeight: Int,
aspectRatio: Size
): Size {
// Collect the supported resolutions that are at least as big as the preview Surface
val bigEnough = ArrayList<Size>()
// Collect the supported resolutions that are smaller than the preview Surface
val notBigEnough = ArrayList<Size>()
val w = aspectRatio.width
val h = aspectRatio.height
for (option in choices) {
if (option.width <= maxWidth && option.height <= maxHeight &&
option.height == option.width * h / w) {
if (option.width >= textureViewWidth && option.height >= textureViewHeight) {
bigEnough.add(option)
} else {
notBigEnough.add(option)
}
}
}
// Pick the smallest of those big enough. If there is no one big enough, pick the
// largest of those not big enough.
if (bigEnough.size > 0) {
return Collections.min(bigEnough, CompareSizesByArea())
} else if (notBigEnough.size > 0) {
return Collections.max(notBigEnough, CompareSizesByArea())
} else {
Log.e(TAG, "Couldn't find any suitable preview size")
return choices[0]
}
}
@JvmStatic fun newInstance(): Camera2BasicFragment = Camera2BasicFragment()
}
}