/*
* Copyright 2024 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 androidx.camera.lifecycle
import android.app.Application
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PackageManager.FEATURE_CAMERA_CONCURRENT
import androidx.annotation.GuardedBy
import androidx.annotation.MainThread
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.annotation.VisibleForTesting
import androidx.camera.core.Camera
import androidx.camera.core.CameraEffect
import androidx.camera.core.CameraFilter
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraInfoUnavailableException
import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraX
import androidx.camera.core.CameraXConfig
import androidx.camera.core.ConcurrentCamera
import androidx.camera.core.ConcurrentCamera.SingleCameraConfig
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageCapture
import androidx.camera.core.InitializationException
import androidx.camera.core.Preview
import androidx.camera.core.UseCase
import androidx.camera.core.UseCaseGroup
import androidx.camera.core.ViewPort
import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_CONCURRENT
import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_SINGLE
import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_UNSPECIFIED
import androidx.camera.core.concurrent.CameraCoordinator.CameraOperatingMode
import androidx.camera.core.impl.CameraConfig
import androidx.camera.core.impl.CameraConfigs
import androidx.camera.core.impl.CameraInternal
import androidx.camera.core.impl.ExtendedCameraConfigProviderStore
import androidx.camera.core.impl.RestrictedCameraInfo
import androidx.camera.core.impl.utils.ContextUtil
import androidx.camera.core.impl.utils.Threads
import androidx.camera.core.impl.utils.executor.CameraXExecutors
import androidx.camera.core.impl.utils.futures.FutureCallback
import androidx.camera.core.impl.utils.futures.FutureChain
import androidx.camera.core.impl.utils.futures.Futures
import androidx.camera.core.internal.CameraUseCaseAdapter
import androidx.camera.lifecycle.ProcessCameraProvider.Companion.getInstance
import androidx.concurrent.futures.CallbackToFutureAdapter
import androidx.core.util.Preconditions
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import com.google.common.util.concurrent.ListenableFuture
import java.util.Objects.requireNonNull
/**
* A singleton which can be used to bind the lifecycle of cameras to any [LifecycleOwner]
* within an application's process.
*
* Only a single process camera provider can exist within a process, and it can be retrieved
* with [getInstance].
*
* Heavyweight resources, such as open and running camera devices, will be scoped to the
* lifecycle provided to [bindToLifecycle]. Other lightweight resources, such as static camera
* characteristics, may be retrieved and cached upon first retrieval of this provider with
* [getInstance], and will persist for the lifetime of the process.
*
* This is the standard provider for applications to use.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
class ProcessCameraProvider private constructor() : LifecycleCameraProvider {
private val mLock = Any()
@GuardedBy("mLock")
private var mCameraXConfigProvider: CameraXConfig.Provider? = null
@GuardedBy("mLock")
private var mCameraXInitializeFuture: ListenableFuture<CameraX>? = null
@GuardedBy("mLock")
private var mCameraXShutdownFuture = Futures.immediateFuture<Void>(null)
private val mLifecycleCameraRepository = LifecycleCameraRepository()
private var mCameraX: CameraX? = null
private var mContext: Context? = null
@GuardedBy("mLock")
private val mCameraInfoMap: MutableMap<CameraUseCaseAdapter.CameraId, RestrictedCameraInfo> =
HashMap()
/**
* Allows shutting down this ProcessCameraProvider instance so a new instance can be
* retrieved by [getInstance].
*
* Once shutdownAsync is invoked, a new instance can be retrieved with [getInstance].
*
* This method should be used for testing purposes only. Along with [configureInstance], this
* allows the process camera provider to be used in test suites which may need to initialize
* CameraX in different ways in between tests.
*
* @return A [ListenableFuture] representing the shutdown status. Cancellation of this
* future is a no-op.
*/
@VisibleForTesting
fun shutdownAsync(): ListenableFuture<Void> {
Threads.runOnMainSync {
unbindAll()
mLifecycleCameraRepository.clear()
}
if (mCameraX != null) {
mCameraX!!.cameraFactory.cameraCoordinator.shutdown()
}
val shutdownFuture =
if (mCameraX != null) mCameraX!!.shutdown() else Futures.immediateFuture<Void>(null)
synchronized(mLock) {
mCameraXConfigProvider = null
mCameraXInitializeFuture = null
mCameraXShutdownFuture = shutdownFuture
mCameraInfoMap.clear()
}
mCameraX = null
mContext = null
return shutdownFuture
}
/**
* Binds the collection of [UseCase] to a [LifecycleOwner].
*
* The state of the lifecycle will determine when the cameras are open, started, stopped
* and closed. When started, the use cases receive camera data.
*
* Binding to a lifecycleOwner in state currently in [Lifecycle.State.STARTED] or greater will
* also initialize and start data capture. If the camera was already running this may cause a
* new initialization to occur temporarily stopping data from the camera before restarting it.
*
* Multiple use cases can be bound via adding them all to a single bindToLifecycle call, or
* by using multiple bindToLifecycle calls. Using a single call that includes all the use
* cases helps to set up a camera session correctly for all uses cases, such as by allowing
* determination of resolutions depending on all the use cases bound being bound.
* If the use cases are bound separately, it will find the supported resolution with the
* priority depending on the binding sequence. If the use cases are bound with a single call,
* it will find the supported resolution with the priority in sequence of [ImageCapture],
* [Preview] and then [ImageAnalysis]. The resolutions that can be supported depends
* on the camera device hardware level that there are some default guaranteed resolutions
* listed in [android.hardware.camera2.CameraDevice.createCaptureSession].
*
* Currently up to 3 use cases may be bound to a [Lifecycle] at any time. Exceeding
* capability of target camera device will throw an IllegalArgumentException.
*
* A UseCase should only be bound to a single lifecycle and camera selector a time.
* Attempting to bind a use case to a lifecycle when it is already bound to another lifecycle
* is an error, and the use case binding will not change. Attempting to bind the same use case
* to multiple camera selectors is also an error and will not change the binding.
*
* If different use cases are bound to different camera selectors that resolve to distinct
* cameras, but the same lifecycle, only one of the cameras will operate at a time. The
* non-operating camera will not become active until it is the only camera with use cases bound.
*
* The [Camera] returned is determined by the given camera selector, plus other
* internal requirements, possibly from use case configurations. The camera returned from
* bindToLifecycle may differ from the camera determined solely by a camera selector. If the
* camera selector can't resolve a valid camera under the requirements, an
* IllegalArgumentException will be thrown.
*
* Only [UseCase] bound to latest active [Lifecycle] can keep alive. [UseCase] bound to other
* [Lifecycle] will be stopped.
*
* @param lifecycleOwner The lifecycleOwner which controls the lifecycle transitions of the use
* cases.
* @param cameraSelector The camera selector which determines the camera to use for set of
* use cases.
* @param useCases The use cases to bind to a lifecycle.
* @return The [Camera] instance which is determined by the camera selector and internal
* requirements.
* @throws IllegalStateException If the use case has already been bound to another lifecycle or
* method is not called on main thread.
* @throws IllegalArgumentException If the provided camera selector is unable to resolve a
* camera to be used for the given use cases.
* @throws UnsupportedOperationException If the camera is configured in concurrent mode.
*/
@MainThread
fun bindToLifecycle(
lifecycleOwner: LifecycleOwner,
cameraSelector: CameraSelector,
vararg useCases: UseCase?
): Camera {
if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
throw UnsupportedOperationException(
"bindToLifecycle for single camera is not supported in concurrent camera mode, " +
"call unbindAll() first"
)
}
cameraOperatingMode = CAMERA_OPERATING_MODE_SINGLE
val camera = bindToLifecycle(
lifecycleOwner, cameraSelector, null, emptyList<CameraEffect>(),
*useCases
)
return camera
}
/**
* Binds a [UseCaseGroup] to a [LifecycleOwner].
*
* Similar to [bindToLifecycle], with the addition that the bound collection of [UseCase] share
* parameters defined by [UseCaseGroup] such as consistent camera sensor rect across all
* [UseCase]s.
*
* If one [UseCase] is in multiple [UseCaseGroup]s, it will be linked to the [UseCaseGroup] in
* the latest [bindToLifecycle] call.
*
* @throws UnsupportedOperationException If the camera is configured in concurrent mode.
*/
@MainThread
fun bindToLifecycle(
lifecycleOwner: LifecycleOwner,
cameraSelector: CameraSelector,
useCaseGroup: UseCaseGroup
): Camera {
if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
throw UnsupportedOperationException(
"bindToLifecycle for single camera is not supported in concurrent camera mode, " +
"call unbindAll() first."
)
}
cameraOperatingMode = CAMERA_OPERATING_MODE_SINGLE
val camera = bindToLifecycle(
lifecycleOwner,
cameraSelector,
useCaseGroup.viewPort,
useCaseGroup.effects,
*useCaseGroup.useCases.toTypedArray<UseCase>()
)
return camera
}
/**
* Binds list of [SingleCameraConfig]s to [LifecycleOwner].
*
* The concurrent camera is only supporting two cameras currently. If the input list of
* [SingleCameraConfig]s have less or more than two [SingleCameraConfig]s,
* [IllegalArgumentException] will be thrown. If cameras are already used by other [UseCase]s,
* [UnsupportedOperationException] will be thrown.
*
* A logical camera is a grouping of two or more of those physical cameras.
* See [Multi-camera API](https://developer.android.com/media/camera/camera2/multi-camera)
*
* If we want to open concurrent logical cameras, which are one front camera and one back
* camera, the device needs to support [PackageManager.FEATURE_CAMERA_CONCURRENT]. To set up
* concurrent logical camera, call [availableConcurrentCameraInfos] to get the list of available
* combinations of concurrent cameras. Each sub-list contains the [CameraInfo]s for a
* combination of cameras that can be operated concurrently. Each logical camera can have its
* own [UseCase]s and [LifecycleOwner]. See
* [CameraX lifecycles]({@docRoot}training/camerax/architecture#lifecycles)
*
* If we want to open concurrent physical cameras, which are two front cameras or two back
* cameras, the device needs to support physical cameras and the capability could be checked via
* [CameraInfo.isLogicalMultiCameraSupported]. Each physical cameras can have its own [UseCase]s
* but needs to have the same [LifecycleOwner], otherwise [IllegalArgumentException] will be
* thrown.
*
* If we want to open one physical camera, for example ultra wide, we just need to set physical
* camera id in [CameraSelector] and bind to lifecycle. All CameraX features will work normally
* when only a single physical camera is used.
*
* If we want to open multiple physical cameras, we need to have multiple [CameraSelector]s,
* each in one [SingleCameraConfig] and set physical camera id, then bind to lifecycle with the
* [SingleCameraConfig]s. Internally each physical camera id will be set on [UseCase], for
* example, [Preview] and call
* [android.hardware.camera2.params.OutputConfiguration.setPhysicalCameraId].
*
* Currently only two physical cameras for the same logical camera id are allowed and the device
* needs to support physical cameras by checking [CameraInfo.isLogicalMultiCameraSupported]. In
* addition, there is no guarantee or API to query whether the device supports multiple physical
* camera opening or not. Internally the library checks
* [android.hardware.camera2.CameraDevice.isSessionConfigurationSupported], if the device does
* not support the multiple physical camera configuration, [IllegalArgumentException] will be
* thrown.
*
* @param singleCameraConfigs Input list of [SingleCameraConfig]s.
* @return Output [ConcurrentCamera] instance.
*
* @throws IllegalArgumentException If less or more than two camera configs are provided.
* @throws UnsupportedOperationException If device is not supporting concurrent camera or
* cameras are already used by other [UseCase]s.
*
* @see ConcurrentCamera
* @see availableConcurrentCameraInfos
* @see CameraInfo.isLogicalMultiCameraSupported
* @see CameraInfo.getPhysicalCameraInfos
*/
@MainThread
fun bindToLifecycle(singleCameraConfigs: List<SingleCameraConfig?>): ConcurrentCamera {
if (singleCameraConfigs.size < 2) {
throw IllegalArgumentException("Concurrent camera needs two camera configs.")
}
if (singleCameraConfigs.size > 2) {
throw IllegalArgumentException(
"Concurrent camera is only supporting two cameras at maximum."
)
}
val firstCameraConfig = singleCameraConfigs[0]!!
val secondCameraConfig = singleCameraConfigs[1]!!
val cameras: MutableList<Camera> = ArrayList()
if (firstCameraConfig.cameraSelector.lensFacing
== secondCameraConfig.cameraSelector.lensFacing) {
if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
throw UnsupportedOperationException(
"Camera is already running, call unbindAll() before binding more cameras."
)
}
if (firstCameraConfig.lifecycleOwner
!= secondCameraConfig.lifecycleOwner ||
firstCameraConfig.useCaseGroup.viewPort
!= secondCameraConfig.useCaseGroup.viewPort ||
firstCameraConfig.useCaseGroup.effects
!= secondCameraConfig.useCaseGroup.effects
) {
throw IllegalArgumentException(
"Two camera configs need to have the same lifecycle owner, view port and " +
"effects."
)
}
val lifecycleOwner = firstCameraConfig.lifecycleOwner
val cameraSelector = firstCameraConfig.cameraSelector
val viewPort = firstCameraConfig.useCaseGroup.viewPort
val effects = firstCameraConfig.useCaseGroup.effects
val useCases: MutableList<UseCase> = ArrayList()
for (config: SingleCameraConfig? in singleCameraConfigs) {
// Connect physical camera id with use case.
for (useCase: UseCase in config!!.useCaseGroup.useCases) {
config.cameraSelector.physicalCameraId?.let { useCase.setPhysicalCameraId(it) }
}
useCases.addAll(config.useCaseGroup.useCases)
}
cameraOperatingMode = CAMERA_OPERATING_MODE_SINGLE
val camera = bindToLifecycle(
lifecycleOwner,
cameraSelector,
viewPort,
effects,
*useCases.toTypedArray<UseCase>()
)
cameras.add(camera)
} else {
if (!mContext!!.packageManager.hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) {
throw UnsupportedOperationException(
"Concurrent camera is not supported on the device."
)
}
if (cameraOperatingMode == CAMERA_OPERATING_MODE_SINGLE) {
throw UnsupportedOperationException(
"Camera is already running, call unbindAll() before binding more cameras."
)
}
val cameraInfosToBind: MutableList<CameraInfo> = ArrayList()
val firstCameraInfo: CameraInfo
val secondCameraInfo: CameraInfo
try {
firstCameraInfo = getCameraInfo(firstCameraConfig.cameraSelector)
secondCameraInfo = getCameraInfo(secondCameraConfig.cameraSelector)
} catch (e: IllegalArgumentException) {
throw IllegalArgumentException("Invalid camera selectors in camera configs.")
}
cameraInfosToBind.add(firstCameraInfo)
cameraInfosToBind.add(secondCameraInfo)
if (activeConcurrentCameraInfos.isNotEmpty() &&
cameraInfosToBind != activeConcurrentCameraInfos
) {
throw UnsupportedOperationException(
"Cameras are already running, call unbindAll() before binding more cameras."
)
}
cameraOperatingMode = CAMERA_OPERATING_MODE_CONCURRENT
for (config: SingleCameraConfig? in singleCameraConfigs) {
val camera = bindToLifecycle(
config!!.lifecycleOwner,
config.cameraSelector,
config.useCaseGroup.viewPort,
config.useCaseGroup.effects,
*config.useCaseGroup.useCases.toTypedArray<UseCase>()
)
cameras.add(camera)
}
activeConcurrentCameraInfos = cameraInfosToBind
}
return ConcurrentCamera(cameras)
}
/**
* Binds [ViewPort] and a collection of [UseCase] to a [LifecycleOwner].
*
* The state of the lifecycle will determine when the cameras are open, started, stopped and
* closed. When started, the use cases receive camera data.
*
* Binding to a [LifecycleOwner] in state currently in [Lifecycle.State.STARTED] or greater will
* also initialize and start data capture. If the camera was already running this may cause a
* new initialization to occur temporarily stopping data from the camera before restarting it.
*
* Multiple use cases can be bound via adding them all to a single [bindToLifecycle] call, or
* by using multiple [bindToLifecycle] calls. Using a single call that includes all the use
* cases helps to set up a camera session correctly for all uses cases, such as by allowing
* determination of resolutions depending on all the use cases bound being bound. If the use
* cases are bound separately, it will find the supported resolution with the priority depending
* on the binding sequence. If the use cases are bound with a single call, it will find the
* supported resolution with the priority in sequence of [ImageCapture], [Preview] and then
* [ImageAnalysis]. The resolutions that can be supported depends on the camera device hardware
* level that there are some default guaranteed resolutions listed in
* [android.hardware.camera2.CameraDevice.createCaptureSession].
*
* Currently up to 3 use cases may be bound to a [Lifecycle] at any time. Exceeding capability
* of target camera device will throw an IllegalArgumentException.
*
* A [UseCase] should only be bound to a single lifecycle and camera selector a time. Attempting
* to bind a use case to a lifecycle when it is already bound to another lifecycle is an error,
* and the use case binding will not change. Attempting to bind the same use case to multiple
* camera selectors is also an error and will not change the binding.
*
* If different use cases are bound to different camera selectors that resolve to distinct
* cameras, but the same lifecycle, only one of the cameras will operate at a time. The
* non-operating camera will not become active until it is the only camera with use cases bound.
*
* The [Camera] returned is determined by the given camera selector, plus other internal
* requirements, possibly from use case configurations. The camera returned from
* [bindToLifecycle] may differ from the camera determined solely by a camera selector. If the
* camera selector can't resolve a camera under the requirements, an [IllegalArgumentException]
* will be thrown.
*
* Only [UseCase] bound to latest active [Lifecycle] can keep alive. [UseCase] bound to other
* [Lifecycle] will be stopped.
*
* @param lifecycleOwner The [LifecycleOwner] which controls the lifecycle transitions of the
* use cases.
* @param cameraSelector The camera selector which determines the camera to use for set of use
* cases.
* @param viewPort The viewPort which represents the visible camera sensor rect.
* @param effects The effects applied to the camera outputs.
* @param useCases The use cases to bind to a lifecycle.
* @return The [Camera] instance which is determined by the camera selector and internal
* requirements.
* @throws IllegalStateException If the use case has already been bound to another lifecycle or
* method is not called on main thread.
* @throws IllegalArgumentException If the provided camera selector is unable to resolve a
* camera to be used for the given use cases.
*/
@Suppress("unused")
internal fun bindToLifecycle(
lifecycleOwner: LifecycleOwner,
cameraSelector: CameraSelector,
viewPort: ViewPort?,
effects: List<CameraEffect?>,
vararg useCases: UseCase?
): Camera {
Threads.checkMainThread()
// TODO(b/153096869): override UseCase's target rotation.
// Get the LifecycleCamera if existed.
val cameraInternal = cameraSelector.select(mCameraX!!.cameraRepository.cameras)
val restrictedCameraInfo = getCameraInfo(cameraSelector) as RestrictedCameraInfo
var lifecycleCameraToBind = mLifecycleCameraRepository.getLifecycleCamera(
lifecycleOwner,
CameraUseCaseAdapter.generateCameraId(restrictedCameraInfo)
)
// Check if there's another camera that has already been bound.
val lifecycleCameras = mLifecycleCameraRepository.lifecycleCameras
useCases.filterNotNull().forEach { useCase ->
for (lifecycleCamera: LifecycleCamera in lifecycleCameras) {
if (lifecycleCamera.isBound(useCase) &&
lifecycleCamera != lifecycleCameraToBind) {
throw IllegalStateException(
String.format(
"Use case %s already bound to a different lifecycle.",
useCase
)
)
}
}
}
// Create the LifecycleCamera if there's no existing one that can be used.
if (lifecycleCameraToBind == null) {
lifecycleCameraToBind = mLifecycleCameraRepository.createLifecycleCamera(
lifecycleOwner,
CameraUseCaseAdapter(
cameraInternal,
restrictedCameraInfo,
mCameraX!!.cameraFactory.cameraCoordinator,
mCameraX!!.cameraDeviceSurfaceManager,
mCameraX!!.defaultConfigFactory
)
)
}
if (useCases.isEmpty()) {
return lifecycleCameraToBind!!
}
mLifecycleCameraRepository.bindToLifecycleCamera(
lifecycleCameraToBind!!,
viewPort,
effects,
listOf(*useCases),
mCameraX!!.cameraFactory.cameraCoordinator
)
return lifecycleCameraToBind
}
/**
* Returns `true` if the [UseCase] is bound to a lifecycle. Otherwise returns `false`.
*
* After binding a use case with [bindToLifecycle], use cases remain bound until the lifecycle
* reaches a [Lifecycle.State.DESTROYED] state or if is unbound by calls to [unbind] or
* [unbindAll].
*/
override fun isBound(useCase: UseCase): Boolean {
for (lifecycleCamera: LifecycleCamera in mLifecycleCameraRepository.lifecycleCameras) {
if (lifecycleCamera.isBound(useCase)) {
return true
}
}
return false
}
/**
* Unbinds all specified use cases from the lifecycle.
*
* This will initiate a close of every open camera which has zero [UseCase] associated with it
* at the end of this call.
*
* If a use case in the argument list is not bound, then it is simply ignored.
*
* After unbinding a UseCase, the UseCase can be and bound to another [Lifecycle] however
* listeners and settings should be reset by the application.
*
* @param useCases The collection of use cases to remove.
* @throws IllegalStateException If not called on main thread.
* @throws UnsupportedOperationException If called in concurrent mode.
*/
@MainThread
override fun unbind(vararg useCases: UseCase?) {
Threads.checkMainThread()
if (cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT) {
throw UnsupportedOperationException(
"Unbind usecase is not supported in concurrent camera mode, call unbindAll() first."
)
}
mLifecycleCameraRepository.unbind(listOf(*useCases))
}
/**
* Unbinds all use cases from the lifecycle and removes them from CameraX.
*
* This will initiate a close of every currently open camera.
*
* @throws IllegalStateException If not called on main thread.
*/
@MainThread
override fun unbindAll() {
Threads.checkMainThread()
cameraOperatingMode = CAMERA_OPERATING_MODE_UNSPECIFIED
mLifecycleCameraRepository.unbindAll()
}
/** {@inheritDoc} */
@Throws(CameraInfoUnavailableException::class)
override fun hasCamera(cameraSelector: CameraSelector): Boolean {
try {
cameraSelector.select(mCameraX!!.cameraRepository.cameras)
} catch (e: IllegalArgumentException) {
return false
}
return true
}
/**
* Returns [CameraInfo] instances of the available cameras.
*
* The available cameras include all the available cameras on the device, or only those
* selected through [androidx.camera.core.CameraXConfig.Builder.setAvailableCamerasLimiter].
*
* While iterating through all the available [CameraInfo], if one of them meets some predefined
* requirements, a [CameraSelector] that uniquely identifies its camera can be retrieved using
* [CameraInfo.getCameraSelector], which can then be used to bind [use cases][UseCase] to that
* camera.
*
* @return A list of [CameraInfo] instances for the available cameras.
*/
override fun getAvailableCameraInfos(): List<CameraInfo> {
val availableCameraInfos: MutableList<CameraInfo> = ArrayList()
val cameras: Set<CameraInternal> = mCameraX!!.cameraRepository.cameras
for (camera: CameraInternal in cameras) {
availableCameraInfos.add(camera.cameraInfo)
}
return availableCameraInfos
}
val availableConcurrentCameraInfos: List<List<CameraInfo>>
/**
* Returns list of [CameraInfo] instances of the available concurrent cameras.
*
* The available concurrent cameras include all combinations of cameras which could operate
* concurrently on the device. Each list maps to one combination of these camera's [CameraInfo].
*
* For example, to select a front camera and a back camera and bind to [LifecycleOwner] with
* preview [UseCase], this function could be used with [bindToLifecycle].
*
* @sample androidx.camera.lifecycle.samples.bindConcurrentCameraSample
*
* @return List of combinations of [CameraInfo].
*/
get() {
requireNonNull(mCameraX)
requireNonNull(mCameraX!!.cameraFactory.cameraCoordinator)
val concurrentCameraSelectorLists =
mCameraX!!.cameraFactory.cameraCoordinator.concurrentCameraSelectors
val availableConcurrentCameraInfos: MutableList<List<CameraInfo>> = ArrayList()
for (cameraSelectors in concurrentCameraSelectorLists) {
val cameraInfos: MutableList<CameraInfo> = ArrayList()
for (cameraSelector in cameraSelectors) {
var cameraInfo: CameraInfo
try {
cameraInfo = getCameraInfo(cameraSelector)
} catch (e: IllegalArgumentException) {
continue
}
cameraInfos.add(cameraInfo)
}
availableConcurrentCameraInfos.add(cameraInfos)
}
return availableConcurrentCameraInfos
}
/**
* Returns the [CameraInfo] instance of the camera resulted from the specified [CameraSelector].
*
* The returned [CameraInfo] is corresponded to the camera that will be bound when calling
* [bindToLifecycle] with the specified [CameraSelector].
*
* @param cameraSelector The [CameraSelector] to get the [CameraInfo] that is corresponded to.
* @return The corresponding [CameraInfo].
* @throws UnsupportedOperationException If the camera provider is not implemented properly.
* @throws IllegalArgumentException If the given [CameraSelector] can't result in a
* valid camera to provide the [CameraInfo].
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
override fun getCameraInfo(cameraSelector: CameraSelector): CameraInfo {
val cameraInfoInternal = cameraSelector.select(
mCameraX!!.cameraRepository.cameras
).cameraInfoInternal
val cameraConfig = getCameraConfig(cameraSelector, cameraInfoInternal)
val key = CameraUseCaseAdapter.CameraId.create(
cameraInfoInternal.cameraId, cameraConfig.compatibilityId
)
var restrictedCameraInfo: RestrictedCameraInfo?
synchronized(mLock) {
restrictedCameraInfo = mCameraInfoMap[key]
if (restrictedCameraInfo == null) {
restrictedCameraInfo = RestrictedCameraInfo(cameraInfoInternal, cameraConfig)
mCameraInfoMap[key] = restrictedCameraInfo!!
}
}
return restrictedCameraInfo!!
}
val isConcurrentCameraModeOn: Boolean
/**
* Returns whether there is a [ConcurrentCamera] bound.
*
* @return `true` if there is a [ConcurrentCamera] bound, otherwise `false`.
*/
@MainThread
get() = cameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT
private fun getOrCreateCameraXInstance(context: Context): ListenableFuture<CameraX> {
synchronized(mLock) {
if (mCameraXInitializeFuture != null) {
return mCameraXInitializeFuture as ListenableFuture<CameraX>
}
val cameraX = CameraX(context, mCameraXConfigProvider)
mCameraXInitializeFuture =
CallbackToFutureAdapter.getFuture { completer ->
synchronized(mLock) {
val future: ListenableFuture<Void> =
FutureChain.from(mCameraXShutdownFuture).transformAsync(
{ cameraX.initializeFuture },
CameraXExecutors.directExecutor()
)
Futures.addCallback(
future,
object :
FutureCallback<Void?> {
override fun onSuccess(result: Void?) {
completer.set(cameraX)
}
override fun onFailure(t: Throwable) {
completer.setException(t)
}
},
CameraXExecutors.directExecutor()
)
}
"ProcessCameraProvider-initializeCameraX"
}
return mCameraXInitializeFuture as ListenableFuture<CameraX>
}
}
private fun configureInstanceInternal(cameraXConfig: CameraXConfig) {
synchronized(mLock) {
Preconditions.checkNotNull(cameraXConfig)
Preconditions.checkState(
mCameraXConfigProvider == null,
"CameraX has already been configured. To use a different configuration, " +
"shutdown() must be called.")
mCameraXConfigProvider = CameraXConfig.Provider { cameraXConfig }
}
}
private fun getCameraConfig(
cameraSelector: CameraSelector,
cameraInfo: CameraInfo
): CameraConfig {
var cameraConfig: CameraConfig? = null
for (cameraFilter: CameraFilter in cameraSelector.cameraFilterSet) {
if (cameraFilter.identifier != CameraFilter.DEFAULT_ID) {
val extendedCameraConfig = ExtendedCameraConfigProviderStore
.getConfigProvider(cameraFilter.identifier)
.getConfig(cameraInfo, (mContext)!!)
if (extendedCameraConfig == null) { // ignore IDs unrelated to camera configs.
continue
}
// Only allows one camera config now.
if (cameraConfig != null) {
throw IllegalArgumentException(
"Cannot apply multiple extended camera configs at the same time."
)
}
cameraConfig = extendedCameraConfig
}
}
if (cameraConfig == null) {
cameraConfig = CameraConfigs.defaultConfig()
}
return cameraConfig
}
private fun setCameraX(cameraX: CameraX) {
mCameraX = cameraX
}
private fun setContext(context: Context) {
mContext = context
}
@get:CameraOperatingMode
private var cameraOperatingMode: Int
get() {
if (mCameraX == null) {
return CAMERA_OPERATING_MODE_UNSPECIFIED
}
return mCameraX!!.cameraFactory.cameraCoordinator.cameraOperatingMode
}
private set(cameraOperatingMode) {
if (mCameraX == null) {
return
}
mCameraX!!.cameraFactory.cameraCoordinator.cameraOperatingMode = cameraOperatingMode
}
private var activeConcurrentCameraInfos: List<CameraInfo>
get() {
if (mCameraX == null) {
return java.util.ArrayList()
}
return mCameraX!!.cameraFactory.cameraCoordinator.activeConcurrentCameraInfos
}
private set(cameraInfos) {
if (mCameraX == null) {
return
}
mCameraX!!.cameraFactory.cameraCoordinator.activeConcurrentCameraInfos = cameraInfos
}
companion object {
private val sAppInstance = ProcessCameraProvider()
/**
* Retrieves the ProcessCameraProvider associated with the current process.
*
* The instance returned here can be used to bind use cases to any [LifecycleOwner] with
* [bindToLifecycle].
*
* The instance's configuration may be customized by subclassing the application's
* [Application] class and implementing [CameraXConfig.Provider]. For example, the sample
* implements [CameraXConfig.Provider.getCameraXConfig] and initializes this process camera
* provider with a [Camera2 implementation][androidx.camera.camera2.Camera2Config] from
* [androidx.camera.camera2], and with a custom executor.
*
* @sample androidx.camera.lifecycle.samples.getCameraXConfigSample
*
* If it isn't possible to subclass the [Application] class, such as in library code, then
* the singleton can be configured via [configureInstance] before the first invocation of
* `getInstance(context)`, the sample implements a customized camera provider that
* configures the instance before getting it.
*
* @sample androidx.camera.lifecycle.samples.configureAndGetInstanceSample
*
* If no [CameraXConfig.Provider] is implemented by [Application], or if the
* singleton has not been configured via [configureInstance] a default
* configuration will be used.
*
* @return A future which will contain the ProcessCameraProvider. Cancellation of this
* future is a no-op. This future may fail with an [InitializationException] and
* associated cause that can be retrieved by [Throwable.cause]. The cause will be a
* [androidx.camera.core.CameraUnavailableException] if it fails to access any camera
* during initialization.
*
* @param context The application context.
* @throws IllegalStateException if CameraX fails to initialize via a default provider or a
* [CameraXConfig.Provider].
* @see configureInstance
*/
@Suppress("AsyncSuffixFuture")
@JvmStatic
fun getInstance(context: Context): ListenableFuture<ProcessCameraProvider> {
Preconditions.checkNotNull(context)
return Futures.transform(
sAppInstance.getOrCreateCameraXInstance(context),
{ cameraX ->
sAppInstance.setCameraX(cameraX)
sAppInstance.setContext(ContextUtil.getApplicationContext(context))
sAppInstance
},
CameraXExecutors.directExecutor()
)
}
/**
* Perform one-time configuration of the ProcessCameraProvider singleton with the given
* [CameraXConfig].
*
* This method allows configuration of the camera provider via [CameraXConfig]. All
* initialization tasks, such as communicating with the camera service, will be executed
* on the [java.util.concurrent.Executor] set by [CameraXConfig.Builder.setCameraExecutor],
* or by an internally defined executor if none is provided.
*
* This method is not required for every application. If the method is not called and
* [CameraXConfig.Provider] is not implemented in [Application], default configuration will
* be used.
*
* Once this method is called, the instance configured by the given [CameraXConfig] can be
* retrieved with [getInstance]. [CameraXConfig.Provider] implemented in [Application] will
* be ignored.
*
* Configuration can only occur once. Once the ProcessCameraProvider has been configured
* with `configureInstance()` or [getInstance], this method will throw an
* [IllegalStateException]. Because configuration can only occur once, **usage of this
* method from library code is not recommended** as the application owner should ultimately
* be in control of singleton configuration.
*
* @param cameraXConfig configuration options for the singleton process camera provider
* instance.
* @throws IllegalStateException If the camera provider has already been configured by a
* previous call to `configureInstance()` or [getInstance].
*/
@JvmStatic
@ExperimentalCameraProviderConfiguration
fun configureInstance(cameraXConfig: CameraXConfig) {
sAppInstance.configureInstanceInternal(cameraXConfig)
}
}
}