Add adapters to turn UseCases into a CameraGraph
This change wires UseCase(s) together into a 'UseCaseCamera' that
CameraX can interact with through its internal interfaces. Each time a
UseCase is attached / detached the UseCaseCamera is closed and a new
instances is created. This causes the underlying CameraCaptureSession
to be recreated based on the current set of attached UseCases.
Additional changes:
* Refactored most of the direct CameraX interface implementation into
an 'adapter' package.
* Introduced dagger for dependency management and to make testing
easier for several scenarios. These classes live in a config package.
* Refactored the code that interacts with a single CameraGraph into a
UseCaseCamera, and the code that tracks the currently active UseCases
into a UseCaseManager.
* Added several mappings between CameraPipe metadata, and CameraX
internal enums.
Test: Ran CameraX Core app using camera_pipe option.
Test: ./gradlew :camera:camera-camera2-pipe-integration:compileDebugSources
Test: ./gradlew :camera:camera-camera2-pipe-integration:testDebugUnitTest
Test: ./gradlew :camera:camera-camera2-pipe-integration:connectedDebugAndroidTest
Test: ./gradlew :camera:camera-camera2-pipe-integration:lintDebug
Change-Id: Ia14d16d3cbb5c2d96cc139d218ffcf66ba4f2d64
diff --git a/camera/camera-camera2-pipe-integration/build.gradle b/camera/camera-camera2-pipe-integration/build.gradle
index fe1548c..30e18b6 100644
--- a/camera/camera-camera2-pipe-integration/build.gradle
+++ b/camera/camera-camera2-pipe-integration/build.gradle
@@ -18,6 +18,7 @@
import androidx.build.LibraryGroups
import androidx.build.LibraryVersions
import androidx.build.Publish
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import static androidx.build.dependencies.DependenciesKt.*
@@ -25,6 +26,7 @@
id("AndroidXPlugin")
id("com.android.library")
id("kotlin-android")
+ id("kotlin-kapt")
}
apply from: "../camera-camera2-pipe/dependencies.gradle"
@@ -39,14 +41,21 @@
releaseBundleInside(project(path: ':camera:camera-camera2-pipe', configuration: "exportRelease"))
debugBundleInside(project(path: ':camera:camera-camera2-pipe', configuration: "exportDebug"))
+ // Classes and types that are needed at compile & runtime
api("androidx.annotation:annotation:1.1.0")
api(project(":camera:camera-core"))
+
+ // Classes and types that are only needed at runtime
+ implementation(project(":lifecycle:lifecycle-livedata-ktx"))
+ implementation(KOTLIN_COROUTINES_GUAVA)
implementation(KOTLIN_STDLIB)
// Since we jarjar CameraPipe, include the transitive dependencies as implementation
implementation CAMERA_PIPE_DEPS.API
implementation CAMERA_PIPE_DEPS.IMPLEMENTATION
+ kapt(DAGGER_COMPILER)
+
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_RUNNER)
androidTestImplementation(KOTLIN_COROUTINES_ANDROID)
@@ -63,6 +72,20 @@
}
}
+kapt {
+ javacOptions {
+ option("-Adagger.fastInit=enabled")
+ option("-Adagger.fullBindingGraphValidation=ERROR")
+ }
+}
+
+// Allow usage of Kotlin's @OptIn.
+tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions {
+ freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"]
+ }
+}
+
androidx {
name = "Jetpack Camera Camera Pipe Integration Library"
publish = Publish.NONE
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/CameraPipeConfig.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/CameraPipeConfig.kt
index 473559c..bb925bd 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/CameraPipeConfig.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/CameraPipeConfig.kt
@@ -16,9 +16,9 @@
package androidx.camera.camera2.pipe.integration
import androidx.camera.core.CameraXConfig
-import androidx.camera.camera2.pipe.integration.impl.CameraPipeFactory
-import androidx.camera.camera2.pipe.integration.impl.StreamConfigurationMap
-import androidx.camera.camera2.pipe.integration.impl.UseCaseConfigurationMap
+import androidx.camera.camera2.pipe.integration.adapter.CameraFactoryAdapter
+import androidx.camera.camera2.pipe.integration.adapter.CameraSurfaceAdapter
+import androidx.camera.camera2.pipe.integration.adapter.CameraUseCaseAdapter
/**
* Convenience class for generating a pre-populated CameraPipe based [CameraXConfig].
@@ -29,9 +29,9 @@
*/
fun defaultConfig(): CameraXConfig {
return CameraXConfig.Builder()
- .setCameraFactoryProvider(::CameraPipeFactory)
- .setDeviceSurfaceManagerProvider(::StreamConfigurationMap)
- .setUseCaseConfigFactoryProvider(::UseCaseConfigurationMap)
+ .setCameraFactoryProvider(::CameraFactoryAdapter)
+ .setDeviceSurfaceManagerProvider(::CameraSurfaceAdapter)
+ .setUseCaseConfigFactoryProvider(::CameraUseCaseAdapter)
.build()
}
}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
new file mode 100644
index 0000000..dcf14ed5
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.adapter
+
+import android.annotation.SuppressLint
+import android.graphics.Rect
+import android.hardware.camera2.CameraCharacteristics
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.camera2.pipe.impl.Log.warn
+import androidx.camera.camera2.pipe.integration.config.CameraScope
+import androidx.camera.camera2.pipe.integration.impl.CameraState
+import androidx.camera.camera2.pipe.integration.impl.UseCaseManager
+import androidx.camera.camera2.pipe.integration.impl.UseCaseCamera
+import androidx.camera.core.FocusMeteringAction
+import androidx.camera.core.FocusMeteringResult
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.TorchState
+import androidx.camera.core.impl.CameraCaptureResult
+import androidx.camera.core.impl.CameraControlInternal
+import androidx.camera.core.impl.CaptureConfig
+import androidx.camera.core.impl.Config
+import androidx.camera.core.impl.MutableOptionsBundle
+import androidx.camera.core.impl.utils.futures.Futures
+import com.google.common.util.concurrent.ListenableFuture
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+import javax.inject.Provider
+
+/**
+ * Adapt the [CameraControlInternal] interface to [CameraPipe].
+ *
+ * This controller class maintains state as use-cases are attached / detached from the camera as
+ * well as providing access to other utility methods. The primary purpose of this class it to
+ * forward these interactions to the currently configured [UseCaseCamera].
+ */
+@CameraScope
+@OptIn(ExperimentalCoroutinesApi::class)
+class CameraControlAdapter @Inject constructor(
+ lazyCameraMetadata: Provider<CameraMetadata>,
+ private val cameraScope: CoroutineScope,
+ private val cameraState: CameraState,
+ private val useCaseManager: UseCaseManager
+) : CameraControlInternal {
+ private val cameraMetadata by lazy { lazyCameraMetadata.get() }
+ private var interopConfig: Config = MutableOptionsBundle.create()
+ private var imageCaptureFlashMode: Int = ImageCapture.FLASH_MODE_OFF
+
+ override fun getSensorRect(): Rect {
+ return cameraMetadata[CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE]!!
+ }
+
+ override fun addInteropConfig(config: Config) {
+ interopConfig = Config.mergeConfigs(config, interopConfig)
+ }
+
+ override fun clearInteropConfig() {
+ interopConfig = MutableOptionsBundle.create()
+ }
+
+ override fun getInteropConfig(): Config {
+ return interopConfig
+ }
+
+ override fun enableTorch(torch: Boolean): ListenableFuture<Void> {
+ // Launch UNDISPATCHED to preserve interaction order with the camera.
+ return cameraScope.launchAsVoidFuture(start = CoroutineStart.UNDISPATCHED) {
+ useCaseManager.camera?.let {
+ // Tell the camera to turn the torch on / off.
+ val result = it.enableTorchAsync(torch)
+
+ // Update the torch state.
+ withContext(Dispatchers.Main) {
+ cameraState.torchState.value = when (torch) {
+ true -> TorchState.ON
+ false -> TorchState.OFF
+ }
+ }
+
+ // Wait until the command is received by the camera.
+ result.await()
+ }
+ }
+ }
+
+ override fun startFocusAndMetering(
+ action: FocusMeteringAction
+ ): ListenableFuture<FocusMeteringResult> {
+ warn { "TODO: startFocusAndMetering is not yet supported" }
+ return Futures.immediateFuture(FocusMeteringResult.emptyInstance())
+ }
+
+ override fun cancelFocusAndMetering(): ListenableFuture<Void> {
+ warn { "TODO: cancelFocusAndMetering is not yet supported" }
+ return Futures.immediateFuture(null)
+ }
+
+ override fun setZoomRatio(ratio: Float): ListenableFuture<Void> {
+ // Note: The current implementation waits until the update has been *submitted* to the
+ // camera, but does not wait for the total capture result.
+ warn { "TODO: setZoomRatio is not yet supported" }
+ return Futures.immediateFuture(null)
+ }
+
+ override fun setLinearZoom(linearZoom: Float): ListenableFuture<Void> {
+ warn { "TODO: setLinearZoom is not yet supported" }
+ return Futures.immediateFuture(null)
+ }
+
+ override fun getFlashMode(): Int {
+ return imageCaptureFlashMode
+ }
+
+ override fun setFlashMode(flashMode: Int) {
+ warn { "TODO: setFlashMode is not yet supported" }
+ this.imageCaptureFlashMode = flashMode
+ }
+
+ override fun triggerAf(): ListenableFuture<CameraCaptureResult> {
+ warn { "TODO: triggerAf is not yet supported" }
+ return Futures.immediateFuture(CameraCaptureResult.EmptyCameraCaptureResult.create())
+ }
+
+ override fun triggerAePrecapture(): ListenableFuture<CameraCaptureResult> {
+ warn { "TODO: triggerAePrecapture is not yet supported" }
+ return Futures.immediateFuture(CameraCaptureResult.EmptyCameraCaptureResult.create())
+ }
+
+ override fun cancelAfAeTrigger(cancelAfTrigger: Boolean, cancelAePrecaptureTrigger: Boolean) {
+ warn { "TODO: cancelAfAeTrigger is not yet supported" }
+ }
+
+ @SuppressLint("UnsafeExperimentalUsageError")
+ override fun setExposureCompensationIndex(exposure: Int): ListenableFuture<Int> {
+ warn { "TODO: setExposureCompensationIndex is not yet supported" }
+ return Futures.immediateFuture(exposure)
+ }
+
+ override fun submitCaptureRequests(captureConfigs: MutableList<CaptureConfig>) {
+ warn { "TODO: submitCaptureRequests is not yet supported" }
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryAdapter.kt
new file mode 100644
index 0000000..a5676be
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryAdapter.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.adapter
+
+import android.content.Context
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.impl.Debug
+import androidx.camera.camera2.pipe.impl.Log.debug
+import androidx.camera.camera2.pipe.impl.Timestamps
+import androidx.camera.camera2.pipe.impl.Timestamps.measureNow
+import androidx.camera.camera2.pipe.impl.Timestamps.formatMs
+import androidx.camera.camera2.pipe.integration.config.CameraConfig
+import androidx.camera.camera2.pipe.integration.config.CameraAppComponent
+import androidx.camera.camera2.pipe.integration.config.CameraAppConfig
+import androidx.camera.camera2.pipe.integration.config.DaggerCameraAppComponent
+import androidx.camera.core.impl.CameraFactory
+import androidx.camera.core.impl.CameraInternal
+import androidx.camera.core.impl.CameraThreadConfig
+
+/**
+ * The [CameraFactoryAdapter] is responsible for creating the root dagger component that is used
+ * to share resources across Camera instances.
+ */
+class CameraFactoryAdapter(
+ context: Context,
+ threadConfig: CameraThreadConfig
+) : CameraFactory {
+ private val appComponent: CameraAppComponent by lazy {
+ Debug.traceStart { "CameraFactoryAdapter#appComponent" }
+ val start = Timestamps.now()
+ val result = DaggerCameraAppComponent.builder()
+ .config(CameraAppConfig(context, threadConfig))
+ .build()
+ debug { "Created CameraFactoryAdapter in ${start.measureNow().formatMs()}" }
+ Debug.traceStop()
+ result
+ }
+
+ init {
+ debug { "Created CameraFactoryAdapter" }
+ }
+
+ override fun getCamera(cameraId: String): CameraInternal =
+ appComponent.cameraBuilder()
+ .config(CameraConfig(CameraId(cameraId)))
+ .build()
+ .getCameraInternal()
+
+ override fun getAvailableCameraIds(): Set<String> = appComponent.getAvailableCameraIds()
+ override fun getCameraManager(): Any? = appComponent
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt
new file mode 100644
index 0000000..442db04
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInfoAdapter.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.adapter
+
+import android.annotation.SuppressLint
+import android.hardware.camera2.CameraCharacteristics
+import android.view.Surface
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.camera2.pipe.integration.config.CameraConfig
+import androidx.camera.camera2.pipe.integration.config.CameraScope
+import androidx.camera.camera2.pipe.integration.impl.CameraCallbackMap
+import androidx.camera.camera2.pipe.integration.impl.CameraState
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.ExposureState
+import androidx.camera.core.ZoomState
+import androidx.camera.core.impl.CameraCaptureCallback
+import androidx.camera.core.impl.CameraInfoInternal
+import androidx.camera.core.impl.utils.CameraOrientationUtil
+import androidx.lifecycle.LiveData
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Provider
+
+/**
+ * Adapt the [CameraInfoInternal] interface to [CameraPipe].
+ */
+@SuppressLint(
+ "UnsafeExperimentalUsageError" // Suppressed due to experimental ExposureState
+)
+@CameraScope
+class CameraInfoAdapter @Inject constructor(
+ private val lazyCameraMetadata: Provider<CameraMetadata>,
+ private val cameraConfig: CameraConfig,
+ private val cameraState: CameraState,
+ private val cameraCallbackMap: CameraCallbackMap
+) : CameraInfoInternal {
+
+ private val cameraMetadata: CameraMetadata
+ get() = lazyCameraMetadata.get()
+
+ override fun getCameraId(): String = cameraConfig.cameraId.value
+ override fun getLensFacing(): Int? = cameraMetadata[CameraCharacteristics.LENS_FACING]
+ override fun getSensorRotationDegrees(): Int = getSensorRotationDegrees(Surface.ROTATION_0)
+ override fun hasFlashUnit(): Boolean =
+ cameraMetadata[CameraCharacteristics.FLASH_INFO_AVAILABLE]!!
+
+ override fun getSensorRotationDegrees(relativeRotation: Int): Int {
+ val sensorOrientation: Int = cameraMetadata[CameraCharacteristics.SENSOR_ORIENTATION]!!
+ val relativeRotationDegrees =
+ CameraOrientationUtil.surfaceRotationToDegrees(relativeRotation)
+ // Currently this assumes that a back-facing camera is always opposite to the screen.
+ // This may not be the case for all devices, so in the future we may need to handle that
+ // scenario.
+ val lensFacing = lensFacing
+ val isOppositeFacingScreen =
+ lensFacing != null && CameraSelector.LENS_FACING_BACK == lensFacing
+ return CameraOrientationUtil.getRelativeImageRotation(
+ relativeRotationDegrees,
+ sensorOrientation,
+ isOppositeFacingScreen
+ )
+ }
+
+ override fun getZoomState(): LiveData<ZoomState> = cameraState.zoomState
+ override fun getTorchState(): LiveData<Int> = cameraState.torchState
+ @SuppressLint("UnsafeExperimentalUsageError")
+ override fun getExposureState(): ExposureState = cameraState.exposureState.value!!
+
+ override fun addSessionCaptureCallback(executor: Executor, callback: CameraCaptureCallback) =
+ cameraCallbackMap.addCaptureCallback(callback, executor)
+ override fun removeSessionCaptureCallback(callback: CameraCaptureCallback) =
+ cameraCallbackMap.removeCaptureCallback(callback)
+
+ override fun getImplementationType(): String = "CameraPipe"
+ override fun toString(): String = "CameraInfoAdapter<$cameraConfig.cameraId>"
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
new file mode 100644
index 0000000..3174c2c
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraInternalAdapter.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.adapter
+
+import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.camera2.pipe.impl.Log.debug
+import androidx.camera.camera2.pipe.impl.Log.warn
+import androidx.camera.camera2.pipe.integration.config.CameraConfig
+import androidx.camera.camera2.pipe.integration.config.CameraScope
+import androidx.camera.camera2.pipe.integration.impl.UseCaseManager
+import androidx.camera.core.UseCase
+import androidx.camera.core.impl.CameraControlInternal
+import androidx.camera.core.impl.CameraInfoInternal
+import androidx.camera.core.impl.CameraInternal
+import androidx.camera.core.impl.LiveDataObservable
+import androidx.camera.core.impl.Observable
+import androidx.camera.core.impl.Quirks
+import androidx.camera.core.impl.utils.futures.Futures
+import com.google.common.util.concurrent.ListenableFuture
+import kotlinx.atomicfu.atomic
+import javax.inject.Inject
+
+internal val defaultQuirks = Quirks(emptyList())
+internal val cameraAdapterIds = atomic(0)
+
+/**
+ * Adapt the [CameraInternal] class to one or more [CameraPipe] based Camera instances.
+ */
+@CameraScope
+class CameraInternalAdapter @Inject constructor(
+ config: CameraConfig,
+ private val useCaseManager: UseCaseManager,
+ private val cameraInfo: CameraInfoInternal,
+ private val cameraController: CameraControlInternal
+) : CameraInternal {
+ private val cameraId = config.cameraId
+ private val debugId = cameraAdapterIds.incrementAndGet()
+ private val cameraState = LiveDataObservable<CameraInternal.State>()
+
+ init {
+ cameraState.postValue(CameraInternal.State.CLOSED)
+
+ debug { "Created $this for $cameraId" }
+ // TODO: Consider preloading the list of camera ids and metadata.
+ }
+
+ override fun getCameraQuirks(): Quirks {
+ warn { "TODO: Quirks are not yet supported." }
+ return defaultQuirks
+ }
+
+ // Load / unload methods
+ override fun open() {
+ debug { "$this#open" }
+ }
+
+ override fun close() {
+ debug { "$this#close" }
+ }
+
+ override fun release(): ListenableFuture<Void> {
+ warn { "$this#release is not yet implemented." }
+ // TODO: Determine what the correct way to invoke release is.
+ return Futures.immediateFuture(null)
+ }
+
+ override fun getCameraInfoInternal(): CameraInfoInternal = cameraInfo
+ override fun getCameraState(): Observable<CameraInternal.State> = cameraState
+ override fun getCameraControlInternal(): CameraControlInternal = cameraController
+
+ // UseCase attach / detach behaviors.
+ override fun attachUseCases(useCasesToAdd: MutableCollection<UseCase>) {
+ useCaseManager.attach(useCasesToAdd.toList())
+ }
+
+ override fun detachUseCases(useCasesToRemove: MutableCollection<UseCase>) {
+ useCaseManager.detach(useCasesToRemove.toList())
+ }
+
+ // UseCase state callbacks
+ override fun onUseCaseActive(useCase: UseCase) {
+ useCaseManager.enable(useCase)
+ }
+
+ override fun onUseCaseUpdated(useCase: UseCase) {
+ useCaseManager.update(useCase)
+ }
+
+ override fun onUseCaseReset(useCase: UseCase) {
+ useCaseManager.update(useCase)
+ }
+
+ override fun onUseCaseInactive(useCase: UseCase) {
+ useCaseManager.disable(useCase)
+ }
+
+ override fun toString(): String = "CameraInternalAdapter<$cameraId>"
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/StreamConfigurationMap.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraSurfaceAdapter.kt
similarity index 60%
rename from camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/StreamConfigurationMap.kt
rename to camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraSurfaceAdapter.kt
index 56b5966..fec8b5a 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/StreamConfigurationMap.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraSurfaceAdapter.kt
@@ -13,22 +13,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package androidx.camera.camera2.pipe.integration.impl
+package androidx.camera.camera2.pipe.integration.adapter
import android.content.Context
+import android.graphics.ImageFormat
import android.util.Size
-import androidx.camera.camera2.pipe.CameraId
import androidx.camera.camera2.pipe.CameraPipe
import androidx.camera.camera2.pipe.impl.Log.debug
+import androidx.camera.camera2.pipe.integration.config.CameraAppComponent
import androidx.camera.core.impl.CameraDeviceSurfaceManager
import androidx.camera.core.impl.SurfaceConfig
+import androidx.camera.core.impl.SurfaceConfig.ConfigSize
import androidx.camera.core.impl.UseCaseConfig
+internal val MAXIMUM_PREVIEW_SIZE = Size(1920, 1080)
+
/**
- * Provide utilities for interacting with the set of guaranteed stream combinations.
+ * Adapt the [CameraDeviceSurfaceManager] interface to [CameraPipe].
+ *
+ * This class provides Context-specific utility methods for querying and computing supported
+ * outputs.
*/
-class StreamConfigurationMap(context: Context, cameraManager: Any?) : CameraDeviceSurfaceManager {
- private val cameraPipe: CameraPipe = cameraManager as CameraPipe
+class CameraSurfaceAdapter(context: Context, cameraComponent: Any?) :
+ CameraDeviceSurfaceManager {
+ private val component = cameraComponent as CameraAppComponent
init {
debug { "Created StreamConfigurationMap from $context" }
@@ -37,7 +45,7 @@
override fun checkSupported(cameraId: String, surfaceConfigList: List<SurfaceConfig>): Boolean {
// TODO: This method needs to check to see if the list of SurfaceConfig's is in the map of
// guaranteed stream configurations for this camera's support level.
- return cameraPipe.cameras().findAll().contains(CameraId(cameraId))
+ return component.getAvailableCameraIds().contains(cameraId)
}
override fun transformSurfaceConfig(
@@ -48,7 +56,15 @@
// TODO: Many of the "find a stream combination that will work" is already provided by the
// existing camera2 implementation, and this implementation should leverage that work.
- TODO("Not Implemented")
+ val configType = when (imageFormat) {
+ ImageFormat.YUV_420_888 -> SurfaceConfig.ConfigType.YUV
+ ImageFormat.JPEG -> SurfaceConfig.ConfigType.JPEG
+ ImageFormat.RAW_SENSOR -> SurfaceConfig.ConfigType.RAW
+ else -> SurfaceConfig.ConfigType.PRIV
+ }
+
+ val configSize = ConfigSize.PREVIEW
+ return SurfaceConfig.create(configType, configSize)
}
override fun getSuggestedResolutions(
@@ -59,6 +75,10 @@
// TODO: Many of the "find a stream combination that will work" is already provided by the
// existing camera2 implementation, and this implementation should leverage that work.
- TODO("Not Implemented")
+ val sizes: MutableMap<UseCaseConfig<*>, Size> = mutableMapOf()
+ for (config in newUseCaseConfigs) {
+ sizes[config as UseCaseConfig<*>] = MAXIMUM_PREVIEW_SIZE
+ }
+ return sizes
}
}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraUseCaseAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraUseCaseAdapter.kt
new file mode 100644
index 0000000..dcbcba5
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraUseCaseAdapter.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.adapter
+
+import android.content.Context
+import android.graphics.Point
+import android.hardware.camera2.CameraDevice
+import android.util.Size
+import android.view.Display
+import android.view.WindowManager
+import androidx.camera.camera2.pipe.impl.Log.debug
+import androidx.camera.camera2.pipe.impl.Log.info
+import androidx.camera.camera2.pipe.integration.impl.asLandscape
+import androidx.camera.camera2.pipe.integration.impl.minByArea
+import androidx.camera.camera2.pipe.integration.impl.toSize
+import androidx.camera.core.impl.CaptureConfig
+import androidx.camera.core.impl.Config
+import androidx.camera.core.impl.ImageOutputConfig
+import androidx.camera.core.impl.MutableOptionsBundle
+import androidx.camera.core.impl.OptionsBundle
+import androidx.camera.core.impl.SessionConfig
+import androidx.camera.core.impl.UseCaseConfig
+import androidx.camera.core.impl.UseCaseConfigFactory
+
+/**
+ * This class builds [Config] objects for a given [UseCaseConfigFactory.CaptureType].
+ *
+ * This includes things like default template and session parameters, as well as maximum resolution
+ * and aspect ratios for the display.
+ */
+class CameraUseCaseAdapter(context: Context) : UseCaseConfigFactory {
+
+ private val display: Display by lazy {
+ @Suppress("deprecation")
+ (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay!!
+ }
+
+ init {
+ if (context === context.applicationContext) {
+ info {
+ "The provided context ($context) is application scoped and will be used to infer " +
+ "the default display for computing the default preview size, orientation, " +
+ "and default aspect ratio for UseCase outputs."
+ }
+ }
+ debug { "Created UseCaseConfigurationMap" }
+ }
+
+ /**
+ * Returns the configuration for the given capture type, or `null` if the
+ * configuration cannot be produced.
+ */
+ override fun getConfig(captureType: UseCaseConfigFactory.CaptureType): Config? {
+ debug { "Creating config for $captureType" }
+
+ val mutableConfig = MutableOptionsBundle.create()
+ val sessionBuilder = SessionConfig.Builder()
+ // TODO(b/114762170): Must set to preview here until we allow for multiple template
+ // types
+ sessionBuilder.setTemplateType(CameraDevice.TEMPLATE_PREVIEW)
+ mutableConfig.insertOption(
+ UseCaseConfig.OPTION_DEFAULT_SESSION_CONFIG,
+ sessionBuilder.build()
+ )
+ val captureBuilder = CaptureConfig.Builder()
+ when (captureType) {
+ UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE ->
+ captureBuilder.templateType = CameraDevice.TEMPLATE_STILL_CAPTURE
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ UseCaseConfigFactory.CaptureType.IMAGE_ANALYSIS,
+ UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE ->
+ captureBuilder.templateType = CameraDevice.TEMPLATE_PREVIEW
+ }
+ mutableConfig.insertOption(
+ UseCaseConfig.OPTION_DEFAULT_CAPTURE_CONFIG,
+ captureBuilder.build()
+ )
+
+ // Only CAPTURE_TYPE_IMAGE_CAPTURE has its own ImageCaptureOptionUnpacker. Other
+ // capture types all use the standard Camera2CaptureOptionUnpacker.
+ mutableConfig.insertOption(
+ UseCaseConfig.OPTION_CAPTURE_CONFIG_UNPACKER,
+ DefaultCaptureOptionsUnpacker
+ )
+ mutableConfig.insertOption(
+ UseCaseConfig.OPTION_SESSION_CONFIG_UNPACKER,
+ DefaultSessionOptionsUnpacker
+ )
+
+ if (captureType == UseCaseConfigFactory.CaptureType.PREVIEW) {
+ mutableConfig.insertOption(
+ ImageOutputConfig.OPTION_MAX_RESOLUTION,
+ getPreviewSize()
+ )
+ }
+
+ mutableConfig.insertOption(
+ ImageOutputConfig.OPTION_TARGET_ROTATION,
+ display.rotation
+ )
+ return OptionsBundle.from(mutableConfig)
+ }
+
+ /**
+ * Returns the device's screen resolution, or 1080p, whichever is smaller.
+ */
+ private fun getPreviewSize(): Size? {
+ val displaySize = Point()
+ display.getRealSize(displaySize)
+ return minByArea(MAXIMUM_PREVIEW_SIZE, displaySize.toSize().asLandscape())
+ }
+
+ object DefaultCaptureOptionsUnpacker : CaptureConfig.OptionUnpacker {
+ override fun unpack(config: UseCaseConfig<*>, builder: CaptureConfig.Builder) {
+ // Unused.
+ }
+ }
+
+ object DefaultSessionOptionsUnpacker : SessionConfig.OptionUnpacker {
+ override fun unpack(config: UseCaseConfig<*>, builder: SessionConfig.Builder) {
+ // Unused.
+ }
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CaptureResultAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CaptureResultAdapter.kt
new file mode 100644
index 0000000..63f471f
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CaptureResultAdapter.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.adapter
+
+import android.hardware.camera2.CaptureResult
+import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.camera2.pipe.FrameInfo
+import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.RequestMetadata
+import androidx.camera.camera2.pipe.impl.Log
+import androidx.camera.camera2.pipe.integration.impl.CAMERAX_TAG_BUNDLE
+import androidx.camera.core.impl.CameraCaptureMetaData.AeState
+import androidx.camera.core.impl.CameraCaptureMetaData.AfMode
+import androidx.camera.core.impl.CameraCaptureMetaData.AfState
+import androidx.camera.core.impl.CameraCaptureMetaData.AwbState
+import androidx.camera.core.impl.CameraCaptureMetaData.FlashState
+import androidx.camera.core.impl.CameraCaptureResult
+import androidx.camera.core.impl.TagBundle
+
+/**
+ * Adapts the [CameraCaptureResult] interface to [CameraPipe].
+ */
+class CaptureResultAdapter(
+ private val requestMetadata: RequestMetadata,
+ private val frameNumber: FrameNumber,
+ private val result: FrameInfo
+) : CameraCaptureResult {
+ override fun getAfMode(): AfMode =
+ when (val mode = result.metadata[CaptureResult.CONTROL_AF_MODE]) {
+ CaptureResult.CONTROL_AF_MODE_OFF,
+ CaptureResult.CONTROL_AF_MODE_EDOF -> AfMode.OFF
+ CaptureResult.CONTROL_AF_MODE_AUTO,
+ CaptureResult.CONTROL_AF_MODE_MACRO -> AfMode.ON_MANUAL_AUTO
+ CaptureResult.CONTROL_AF_MODE_CONTINUOUS_PICTURE,
+ CaptureResult.CONTROL_AF_MODE_CONTINUOUS_VIDEO -> AfMode.ON_CONTINUOUS_AUTO
+ null -> AfMode.UNKNOWN
+ else -> {
+ Log.debug { "Unknown AF mode ($mode) for $frameNumber!" }
+ AfMode.UNKNOWN
+ }
+ }
+
+ override fun getAfState(): AfState =
+ when (val state = result.metadata[CaptureResult.CONTROL_AF_STATE]) {
+ CaptureResult.CONTROL_AF_STATE_INACTIVE -> AfState.INACTIVE
+ CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN,
+ CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN,
+ CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED -> AfState.SCANNING
+ CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED -> AfState.LOCKED_FOCUSED
+ CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED -> AfState.LOCKED_NOT_FOCUSED
+ CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED -> AfState.FOCUSED
+ null -> AfState.UNKNOWN
+ else -> {
+ Log.debug { "Unknown AF state ($state) for $frameNumber!" }
+ AfState.UNKNOWN
+ }
+ }
+
+ override fun getAeState(): AeState =
+ when (val state = result.metadata[CaptureResult.CONTROL_AE_STATE]) {
+ CaptureResult.CONTROL_AE_STATE_INACTIVE -> AeState.INACTIVE
+ CaptureResult.CONTROL_AE_STATE_SEARCHING,
+ CaptureResult.CONTROL_AE_STATE_PRECAPTURE -> AeState.SEARCHING
+ CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED -> AeState.FLASH_REQUIRED
+ CaptureResult.CONTROL_AE_STATE_CONVERGED -> AeState.CONVERGED
+ CaptureResult.CONTROL_AE_STATE_LOCKED -> AeState.LOCKED
+ null -> AeState.UNKNOWN
+ else -> {
+ Log.debug { "Unknown AE state ($state) for $frameNumber!" }
+ AeState.UNKNOWN
+ }
+ }
+
+ override fun getAwbState(): AwbState =
+ when (val state = result.metadata[CaptureResult.CONTROL_AWB_STATE]) {
+ CaptureResult.CONTROL_AWB_STATE_INACTIVE -> AwbState.INACTIVE
+ CaptureResult.CONTROL_AWB_STATE_SEARCHING -> AwbState.METERING
+ CaptureResult.CONTROL_AWB_STATE_CONVERGED -> AwbState.CONVERGED
+ CaptureResult.CONTROL_AWB_STATE_LOCKED -> AwbState.LOCKED
+ null -> AwbState.UNKNOWN
+ else -> {
+ Log.debug { "Unknown AWB state ($state) for $frameNumber!" }
+ AwbState.UNKNOWN
+ }
+ }
+
+ override fun getFlashState(): FlashState =
+ when (val state = result.metadata[CaptureResult.FLASH_STATE]) {
+ CaptureResult.FLASH_STATE_UNAVAILABLE,
+ CaptureResult.FLASH_STATE_CHARGING -> FlashState.NONE
+ CaptureResult.FLASH_STATE_READY -> FlashState.READY
+ CaptureResult.FLASH_STATE_FIRED,
+ CaptureResult.FLASH_STATE_PARTIAL -> FlashState.FIRED
+ null -> FlashState.UNKNOWN
+ else -> {
+ Log.debug { "Unknown flash state ($state) for $frameNumber!" }
+ FlashState.UNKNOWN
+ }
+ }
+
+ override fun getTimestamp(): Long {
+ return result.metadata.getOrDefault(CaptureResult.SENSOR_TIMESTAMP, -1L)
+ }
+
+ override fun getTagBundle(): TagBundle {
+ return requestMetadata.getOrDefault(CAMERAX_TAG_BUNDLE, TagBundle.emptyBundle())
+ }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CoroutineAdapters.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CoroutineAdapters.kt
new file mode 100644
index 0000000..96c3dc8
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CoroutineAdapters.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.adapter
+
+import com.google.common.util.concurrent.ListenableFuture
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.async
+import kotlinx.coroutines.guava.asListenableFuture
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+
+/**
+ * This allows a java method that returns a ListenableFuture<Void> to be implemented by calling a
+ * suspend function.
+ *
+ * Exceptions thrown from the coroutine scope are propagated to the returned future.
+ * Canceling the future will attempt to cancel the coroutine.
+ */
+fun CoroutineScope.launchAsVoidFuture(
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+ block: suspend CoroutineScope.() -> Unit
+): ListenableFuture<Void> {
+ // TODO: This method currently uses guava.asListenableFuture. This may be an expensive
+ // dependency to take on. We may need to evaluate this.
+ @Suppress("UNCHECKED_CAST")
+ return this.async(context = context, start = start, block = block)
+ .asListenableFuture() as ListenableFuture<Void>
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/ExposureStateAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/ExposureStateAdapter.kt
new file mode 100644
index 0000000..bb05d8d
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/ExposureStateAdapter.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.adapter
+
+import android.annotation.SuppressLint
+import android.hardware.camera2.CameraCharacteristics
+import android.util.Range
+import android.util.Rational
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.core.ExposureState
+
+internal val EMPTY_RANGE = Range(0, 0)
+
+/** Adapt [ExposureState] to a [CameraMetadata] instance. */
+@SuppressLint("UnsafeExperimentalUsageError")
+class ExposureStateAdapter(
+ private val cameraMetadata: CameraMetadata,
+ private val exposureCompensation: Int
+) : ExposureState {
+ override fun isExposureCompensationSupported(): Boolean {
+ val range = exposureCompensationRange
+ return range.lower != 0 && range.upper != 0
+ }
+
+ override fun getExposureCompensationIndex(): Int = exposureCompensation
+ override fun getExposureCompensationStep(): Rational {
+ if (!isExposureCompensationSupported) {
+ return Rational.ZERO
+ }
+ return cameraMetadata[CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP]!!
+ }
+
+ override fun getExposureCompensationRange(): Range<Int> {
+ return cameraMetadata[CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE]
+ ?: EMPTY_RANGE
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/ZoomStateAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/ZoomStateAdapter.kt
new file mode 100644
index 0000000..4d42273
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/ZoomStateAdapter.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.adapter
+
+import android.annotation.SuppressLint
+import android.hardware.camera2.CameraCharacteristics
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.core.ZoomState
+
+/**
+ * Adapt [ZoomState] to a [CameraMetadata] instance.
+ */
+@SuppressLint("UnsafeExperimentalUsageError")
+class ZoomStateAdapter(
+ cameraMetadata: CameraMetadata,
+ private val zoomRatio: Float
+) : ZoomState {
+ // TODO: Zoom state has API-specific compat requirements. This is a placeholder for newer
+ // android API versions and will require compat changes to support older versions.
+ private val zoomRange = cameraMetadata[CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE]!!
+
+ override fun getMaxZoomRatio(): Float = zoomRange.upper as Float
+ override fun getMinZoomRatio(): Float = zoomRange.lower as Float
+ override fun getLinearZoom(): Float {
+ val range = zoomRange.upper - zoomRange.lower
+ if (range > 0) {
+ return (zoomRatio - zoomRange.lower) / range
+ }
+ return 1.0f
+ }
+ override fun getZoomRatio(): Float = zoomRatio
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/package-info.java b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/package-info.java
new file mode 100644
index 0000000..f7a9ea2
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2020 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.
+ */
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+package androidx.camera.camera2.pipe.integration.adapter;
+
+import androidx.annotation.RestrictTo;
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraAppConfig.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraAppConfig.kt
new file mode 100644
index 0000000..d8eadf0
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraAppConfig.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.config
+
+import android.content.Context
+import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.core.impl.CameraThreadConfig
+import androidx.camera.core.impl.CameraFactory
+import dagger.Component
+import dagger.Module
+import dagger.Provides
+import javax.inject.Singleton
+
+/** Dependency bindings for adapting a [CameraFactory] instance to [CameraPipe] */
+@Module(
+ subcomponents = [CameraComponent::class]
+)
+abstract class CameraAppModule {
+ companion object {
+ @Singleton
+ @Provides
+ fun provideCameraPipe(context: Context): CameraPipe {
+ return CameraPipe(CameraPipe.Config(appContext = context.applicationContext))
+ }
+
+ @Provides
+ fun provideAvailableCameraIds(cameraPipe: CameraPipe): Set<String> {
+ return cameraPipe.cameras().findAll().map { it.value }.toSet()
+ }
+ }
+}
+
+/** Configuration properties that are shared across this app process */
+@Module
+class CameraAppConfig(
+ private val context: Context,
+ private val threadConfig: CameraThreadConfig
+) {
+ @Provides
+ fun provideContext(): Context = context
+}
+
+/** Dagger component for Application (Process) scoped dependencies. */
+@Singleton
+@Component(
+ modules = [
+ CameraAppModule::class,
+ CameraAppConfig::class
+ ]
+)
+interface CameraAppComponent {
+ fun cameraBuilder(): CameraComponent.Builder
+ fun getAvailableCameraIds(): Set<String>
+
+ @Component.Builder
+ interface Builder {
+ fun config(config: CameraAppConfig): Builder
+ fun build(): CameraAppComponent
+ }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraConfig.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraConfig.kt
new file mode 100644
index 0000000..803d605
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraConfig.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.config
+
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.camera2.pipe.integration.adapter.CameraControlAdapter
+import androidx.camera.camera2.pipe.integration.adapter.CameraInfoAdapter
+import androidx.camera.camera2.pipe.integration.adapter.CameraInternalAdapter
+import androidx.camera.core.impl.CameraControlInternal
+import androidx.camera.core.impl.CameraInfoInternal
+import androidx.camera.core.impl.CameraInternal
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.Subcomponent
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import javax.inject.Scope
+
+@Scope
+annotation class CameraScope
+
+/** Dependency bindings for adapting an individual [CameraInternal] instance to [CameraPipe] */
+@Module(
+ subcomponents = [UseCaseCameraComponent::class]
+)
+abstract class CameraModule {
+ companion object {
+ @CameraScope
+ @Provides
+ fun provideCameraCoroutineScope(cameraConfig: CameraConfig): CoroutineScope {
+ // TODO: Dispatchers.Default is the standard kotlin coroutine executor for background
+ // work, but we may want to pass something in.
+ return CoroutineScope(
+ Job() +
+ Dispatchers.Default +
+ CoroutineName("CXCP-Camera-${cameraConfig.cameraId.value}")
+ )
+ }
+
+ @Provides
+ fun provideCameraMetadata(cameraPipe: CameraPipe, config: CameraConfig): CameraMetadata =
+ cameraPipe.cameras().awaitMetadata(config.cameraId)
+ }
+
+ @Binds
+ abstract fun bindCameraInternal(adapter: CameraInternalAdapter): CameraInternal
+
+ @Binds
+ abstract fun bindCameraInfoInternal(adapter: CameraInfoAdapter): CameraInfoInternal
+
+ @Binds
+ abstract fun bindCameraControlInternal(adapter: CameraControlAdapter): CameraControlInternal
+}
+
+/** Configuration properties used when creating a [CameraInternal] instance. */
+@Module
+class CameraConfig(val cameraId: CameraId) {
+ @Provides
+ fun provideCameraConfig(): CameraConfig = this
+}
+
+/** Dagger subcomponent for a single [CameraInternal] instance. */
+@CameraScope
+@Subcomponent(
+ modules = [
+ CameraModule::class,
+ CameraConfig::class
+ ]
+)
+interface CameraComponent {
+ @Subcomponent.Builder
+ interface Builder {
+ fun config(config: CameraConfig): Builder
+ fun build(): CameraComponent
+ }
+
+ fun getCameraInternal(): CameraInternal
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt
new file mode 100644
index 0000000..8ea76cf
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/UseCaseCameraConfig.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.config
+
+import androidx.camera.camera2.pipe.integration.impl.UseCaseCamera
+import androidx.camera.core.UseCase
+import dagger.Module
+import dagger.Provides
+import dagger.Subcomponent
+import javax.inject.Scope
+
+@Scope
+annotation class UseCaseCameraScope
+
+/** Dependency bindings for building a [UseCaseCamera] */
+@Module(includes = [UseCaseCamera.Bindings::class])
+abstract class UseCaseCameraModule {
+ // Used for dagger provider methods that are static.
+ companion object
+}
+
+/** Dagger module for binding the [UseCase]'s to the [UseCaseCamera]. */
+@Module
+class UseCaseCameraConfig(
+ private val useCases: List<UseCase>
+) {
+ @UseCaseCameraScope
+ @Provides
+ fun provideUseCaseList(): java.util.ArrayList<UseCase> {
+ return java.util.ArrayList(useCases)
+ }
+}
+
+/** Dagger subcomponent for a single [UseCaseCamera] instance. */
+@UseCaseCameraScope
+@Subcomponent(
+ modules = [
+ UseCaseCameraModule::class,
+ UseCaseCameraConfig::class
+ ]
+)
+interface UseCaseCameraComponent {
+ fun getUseCaseCamera(): UseCaseCamera
+
+ @Subcomponent.Builder
+ interface Builder {
+ fun config(config: UseCaseCameraConfig): Builder
+ fun build(): UseCaseCameraComponent
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/package-info.java b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/package-info.java
new file mode 100644
index 0000000..b7d2f6a
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2020 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.
+ */
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+package androidx.camera.camera2.pipe.integration.config;
+
+import androidx.annotation.RestrictTo;
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraAdaptor.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraAdaptor.kt
deleted file mode 100644
index 3d24397..0000000
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraAdaptor.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2020 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.camera2.pipe.integration.impl
-
-import androidx.camera.camera2.pipe.CameraId
-import androidx.camera.camera2.pipe.CameraPipe
-import androidx.camera.camera2.pipe.impl.Log.debug
-import androidx.camera.core.UseCase
-import androidx.camera.core.impl.CameraControlInternal
-import androidx.camera.core.impl.CameraInfoInternal
-import androidx.camera.core.impl.CameraInternal
-import androidx.camera.core.impl.Observable
-import androidx.camera.core.impl.Quirks
-import androidx.camera.core.impl.utils.futures.Futures
-import com.google.common.util.concurrent.ListenableFuture
-
-/**
- * Adapt the [CameraInternal] class to one or more [CameraPipe] based Camera instances.
- */
-class CameraAdaptor(
- private val cameraPipe: CameraPipe,
- private val cameraId: CameraId
-) : CameraInternal {
-
- init {
- debug { "Created CameraAdaptor from $cameraPipe for $cameraId" }
- // TODO: Consider preloading the list of camera ids and metadata.
- }
-
- // Load / unload methods
- override fun open() {
- TODO("Not yet implemented")
- }
-
- override fun close() {
- TODO("Not yet implemented")
- }
-
- override fun release(): ListenableFuture<Void> {
- // TODO: Determine what the correct way to invoke release is.
- return Futures.immediateFuture(null)
- }
-
- // Static properties of this camera
- override fun getCameraInfoInternal(): CameraInfoInternal {
- TODO("Not yet implemented")
- }
-
- override fun getCameraQuirks(): Quirks {
- TODO("Not yet implemented")
- }
-
- // Controls for interacting with or observing the state of the camera.
- override fun getCameraState(): Observable<CameraInternal.State> {
- TODO("Not yet implemented")
- }
-
- override fun getCameraControlInternal(): CameraControlInternal {
- TODO("Not yet implemented")
- }
-
- // UseCase attach / detach behaviors.
- override fun attachUseCases(useCases: MutableCollection<UseCase>) {
- TODO("Not yet implemented")
- }
-
- override fun detachUseCases(useCases: MutableCollection<UseCase>) {
- TODO("Not yet implemented")
- }
-
- // UseCase state callbacks
- override fun onUseCaseActive(useCase: UseCase) {
- TODO("Not yet implemented")
- }
-
- override fun onUseCaseUpdated(useCase: UseCase) {
- TODO("Not yet implemented")
- }
-
- override fun onUseCaseReset(useCase: UseCase) {
- TODO("Not yet implemented")
- }
-
- override fun onUseCaseInactive(useCase: UseCase) {
- TODO("Not yet implemented")
- }
-}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraCallbackMap.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraCallbackMap.kt
new file mode 100644
index 0000000..1619e7f
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraCallbackMap.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.impl
+
+import android.hardware.camera2.CaptureFailure
+import androidx.camera.camera2.pipe.FrameInfo
+import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestMetadata
+import androidx.camera.camera2.pipe.integration.adapter.CaptureResultAdapter
+import androidx.camera.camera2.pipe.integration.config.CameraScope
+import androidx.camera.core.impl.CameraCaptureCallback
+import androidx.camera.core.impl.CameraCaptureFailure
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * A map of [CameraCaptureCallback] that are invoked on each [Request].
+ */
+@CameraScope
+class CameraCallbackMap @Inject constructor() : Request.Listener {
+ private val callbackMap = mutableMapOf<CameraCaptureCallback, Executor>()
+ @Volatile
+ private var callbacks: Map<CameraCaptureCallback, Executor> = mapOf()
+
+ fun addCaptureCallback(callback: CameraCaptureCallback, executor: Executor) {
+ check(!callbacks.contains(callback)) { "$callback was already registered!" }
+
+ synchronized(callbackMap) {
+ callbackMap[callback] = executor
+ callbacks = callbackMap.toMap()
+ }
+ }
+
+ fun removeCaptureCallback(callback: CameraCaptureCallback) {
+ synchronized(callbackMap) {
+ callbackMap.remove(callback)
+ callbacks = callbackMap.toMap()
+ }
+ }
+
+ override fun onComplete(
+ requestMetadata: RequestMetadata,
+ frameNumber: FrameNumber,
+ result: FrameInfo
+ ) {
+ val captureResult = CaptureResultAdapter(requestMetadata, frameNumber, result)
+ for ((callback, executor) in callbacks) {
+ executor.execute { callback.onCaptureCompleted(captureResult) }
+ }
+ }
+
+ override fun onFailed(
+ requestMetadata: RequestMetadata,
+ frameNumber: FrameNumber,
+ captureFailure: CaptureFailure
+ ) {
+ val failure = CameraCaptureFailure(CameraCaptureFailure.Reason.ERROR)
+ for ((callback, executor) in callbacks) {
+ executor.execute { callback.onCaptureFailed(failure) }
+ }
+ }
+
+ override fun onAborted(request: Request) {
+ for ((callback, executor) in callbacks) {
+ executor.execute { callback.onCaptureCancelled() }
+ }
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraPipeFactory.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraPipeFactory.kt
deleted file mode 100644
index 071da5d..0000000
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraPipeFactory.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2020 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.camera2.pipe.integration.impl
-
-import android.content.Context
-import androidx.camera.camera2.pipe.CameraId
-import androidx.camera.camera2.pipe.CameraPipe
-import androidx.camera.camera2.pipe.impl.Debug
-import androidx.camera.camera2.pipe.impl.Log.debug
-import androidx.camera.camera2.pipe.impl.Timestamps
-import androidx.camera.camera2.pipe.impl.Timestamps.measureNow
-import androidx.camera.camera2.pipe.impl.Timestamps.formatMs
-import androidx.camera.core.impl.CameraFactory
-import androidx.camera.core.impl.CameraInternal
-import androidx.camera.core.impl.CameraThreadConfig
-
-/**
- * The CameraPipeCameraFactory is responsible for creating and configuring CameraPipe for CameraX.
- */
-class CameraPipeFactory(
- context: Context,
- threadConfig: CameraThreadConfig
-) : CameraFactory {
- // Lazily create and configure a CameraPipe instance.
- private val cameraPipe: CameraPipe by lazy {
- Debug.traceStart { "CameraPipeCameraFactory#cameraPipe" }
- val result: CameraPipe?
- val start = Timestamps.now()
-
- // TODO: CameraPipe should find a way to make sure callbacks are executed on the configured
- // executors that are provided in `threadConfig`
- debug { "TODO: Use $threadConfig if defined" }
-
- result = CameraPipe(CameraPipe.Config(appContext = context.applicationContext))
- debug { "Created CameraPipe in ${start.measureNow().formatMs()}" }
- Debug.traceStop()
- result
- }
-
- init {
- debug { "Created CameraPipeCameraFactory" }
- // TODO: Consider preloading the list of camera ids and metadata.
- }
-
- override fun getCamera(cameraId: String): CameraInternal {
- // TODO: The CameraInternal object is an facade that covers most of the high level camera
- // state and interactions. CameraInternal objects are persistent across camera switches.
-
- return CameraAdaptor(cameraPipe, CameraId(cameraId))
- }
-
- override fun getAvailableCameraIds(): Set<String> {
- // TODO: This may need some amount of work to limit the returned values well behaved "Front"
- // and "Back" camera devices.
- return cameraPipe.cameras().findAll().map { it.value }.toSet()
- }
-
- override fun getCameraManager(): Any? {
- // Note: This object is passed around as an untyped parameter when constructing a few
- // objects (Such as `DeviceSurfaceManagerProvider`). It's better to rely on the parameter
- // passing than to try to turn this object into a singleton.
- return cameraPipe
- }
-}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraState.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraState.kt
new file mode 100644
index 0000000..e66deba
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CameraState.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.impl
+
+import android.annotation.SuppressLint
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.integration.adapter.ExposureStateAdapter
+import androidx.camera.camera2.pipe.integration.adapter.ZoomStateAdapter
+import androidx.camera.camera2.pipe.integration.config.CameraScope
+import androidx.camera.core.ExposureState
+import androidx.camera.core.ZoomState
+import androidx.lifecycle.MutableLiveData
+import javax.inject.Inject
+import javax.inject.Provider
+
+/**
+ * [CameraState] caches and updates based on callbacks from the active CameraGraph.
+ */
+@SuppressLint("UnsafeExperimentalUsageError")
+@CameraScope
+class CameraState @Inject constructor(
+ private val cameraMetadata: Provider<CameraMetadata>,
+) {
+ val torchState = MutableLiveData<Int>()
+ val zoomState by lazy {
+ MutableLiveData<ZoomState>(
+ ZoomStateAdapter(
+ cameraMetadata.get(),
+ 1.0f
+ )
+ )
+ }
+ val exposureState by lazy {
+ MutableLiveData<ExposureState>(
+ ExposureStateAdapter(
+ cameraMetadata.get(),
+ 0
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/Sizes.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/Sizes.kt
new file mode 100644
index 0000000..aecf6ab
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/Sizes.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.impl
+
+import android.graphics.Point
+import android.util.Size
+
+fun Size.area(): Int = this.width * this.height
+fun Size.asLandscape(): Size =
+ if (this.width >= this.height) this else Size(this.height, this.width)
+fun Size.asPortrait(): Size =
+ if (this.width <= this.height) this else Size(this.height, this.width)
+
+fun minByArea(left: Size, right: Size) = if (left.area() < right.area()) left else right
+fun maxByArea(left: Size, right: Size) = if (left.area() > right.area()) left else right
+
+fun Point.area(): Int = this.x * this.y
+fun Point.toSize() = Size(this.x, this.y)
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/Tags.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/Tags.kt
new file mode 100644
index 0000000..75a1976
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/Tags.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.impl
+
+import androidx.camera.camera2.pipe.Metadata
+import androidx.camera.core.impl.TagBundle
+
+/** Custom tags that can be passed used by CameraPipe */
+public val CAMERAX_TAG_BUNDLE = Metadata.Key.create<TagBundle>("camerax.tag_bundle")
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
new file mode 100644
index 0000000..088855a
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.impl
+
+import android.hardware.camera2.CameraDevice
+import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.camera2.pipe.FrameNumber
+import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestTemplate
+import androidx.camera.camera2.pipe.StreamConfig
+import androidx.camera.camera2.pipe.StreamFormat
+import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.StreamType
+import androidx.camera.camera2.pipe.TorchState
+import androidx.camera.camera2.pipe.impl.Log.debug
+import androidx.camera.camera2.pipe.integration.config.CameraConfig
+import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
+import androidx.camera.core.UseCase
+import androidx.camera.core.impl.DeferrableSurface
+import dagger.Module
+import dagger.Provides
+import kotlinx.atomicfu.atomic
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+internal val useCaseCameraIds = atomic(0)
+
+/**
+ * API for interacting with a [CameraGraph] that has been configured with a set of [UseCase]'s
+ */
+class UseCaseCamera(
+ private val cameraGraph: CameraGraph,
+ private val useCases: List<UseCase>,
+ private val surfaceToStreamMap: Map<DeferrableSurface, StreamId>,
+ private val cameraScope: CoroutineScope
+) {
+ private val debugId = useCaseCameraIds.incrementAndGet()
+
+ private var _activeUseCases = setOf<UseCase>()
+
+ var activeUseCases: Set<UseCase>
+ get() = _activeUseCases
+ set(value) {
+ // Note: This may be called with the same set of values that was previously set. This
+ // is used as a signal to indicate the properties of the UseCase may have changed.
+ _activeUseCases = value
+ updateUseCases()
+ }
+
+ init {
+ debug { "Configured $this for $useCases" }
+ }
+
+ fun close() {
+ debug { "Closing $this" }
+ cameraGraph.close()
+ }
+
+ suspend fun enableTorchAsync(enabled: Boolean): Deferred<FrameNumber> {
+ return cameraGraph.acquireSession().use {
+ it.setTorch(
+ when (enabled) {
+ true -> TorchState.ON
+ false -> TorchState.OFF
+ }
+ )
+ }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun updateUseCases() {
+ val repeatingStreamIds = mutableSetOf<StreamId>()
+ for (useCase in activeUseCases) {
+ val repeatingCapture = useCase.sessionConfig?.repeatingCaptureConfig
+ if (repeatingCapture != null) {
+ for (deferrableSurface in repeatingCapture.surfaces) {
+ val streamId = surfaceToStreamMap[deferrableSurface]
+ if (streamId != null) {
+ repeatingStreamIds.add(streamId)
+ }
+ }
+ }
+ }
+
+ // TODO: This needs to aggregate the current parameters and pass them to the request.
+
+ // In order to preserve ordering, this starts acquiring the session on the current thread,
+ // and will only switch to the cameraScope threads if it needs to suspend. This is important
+ // because access to the cameraGraph is well ordered, and if the coroutine suspends, it will
+ // resume in the order it accessed the cameraGraph.
+ cameraScope.launch(start = CoroutineStart.UNDISPATCHED) {
+ cameraGraph.acquireSession().use {
+ it.setRepeating(
+ Request(
+ streams = repeatingStreamIds.toList()
+ )
+ )
+ }
+ }
+ }
+
+ override fun toString(): String = "UseCaseCamera-$debugId"
+
+ @Module
+ class Bindings {
+ companion object {
+ @UseCaseCameraScope
+ @Provides
+ fun provideCameraGraphController(
+ cameraPipe: CameraPipe,
+ useCases: java.util.ArrayList<UseCase>,
+ cameraConfig: CameraConfig,
+ callbackMap: CameraCallbackMap,
+ coroutineScope: CoroutineScope,
+ ): UseCaseCamera {
+ val streamConfigs = mutableListOf<StreamConfig>()
+ val useCaseMap = mutableMapOf<StreamConfig, UseCase>()
+
+ // TODO: This may need to combine outputs that are (or will) share the same output
+ // imageReader or surface. Right now, each UseCase gets its own [StreamConfig]
+ // TODO: useCases only have a single `attachedSurfaceResolution`, yet they have a
+ // list of deferrableSurfaces.
+ for (useCase in useCases) {
+ val config = StreamConfig(
+ size = useCase.attachedSurfaceResolution!!,
+ format = StreamFormat(useCase.imageFormat),
+ camera = cameraConfig.cameraId,
+ type = StreamType.SURFACE,
+ deferrable = false
+ )
+ streamConfigs.add(config)
+ useCaseMap[config] = useCase
+ }
+
+ // Build up a config (using TEMPLATE_PREVIEW by default)
+ val config = CameraGraph.Config(
+ camera = cameraConfig.cameraId,
+ streams = streamConfigs,
+ listeners = listOf(callbackMap),
+ template = RequestTemplate(CameraDevice.TEMPLATE_PREVIEW)
+ )
+ val graph = cameraPipe.create(config)
+
+ val surfaceToStreamMap = mutableMapOf<DeferrableSurface, StreamId>()
+ for ((streamConfig, useCase) in useCaseMap) {
+ val stream = graph.streams[streamConfig]
+ val useCaseSessionConfig = useCase.sessionConfig
+
+ // TODO: UseCases have inconsistent opinions about how surfaces are handled,
+ // this code assumes only a single surface per UseCase.
+ val deferredSurfaces = useCaseSessionConfig?.surfaces
+ if (stream != null && deferredSurfaces != null && deferredSurfaces.size == 1) {
+ val deferredSurface = deferredSurfaces[0]
+ graph.setSurface(stream.id, deferredSurface.surface.get())
+ surfaceToStreamMap[deferredSurface] = stream.id
+ }
+ }
+
+ graph.start()
+ return UseCaseCamera(graph, useCases, surfaceToStreamMap, coroutineScope)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseConfigurationMap.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseConfigurationMap.kt
deleted file mode 100644
index a0ddab2..0000000
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseConfigurationMap.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2020 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.camera2.pipe.integration.impl
-
-import android.content.Context
-import androidx.camera.camera2.pipe.impl.Log.debug
-import androidx.camera.camera2.pipe.impl.Log.info
-import androidx.camera.core.impl.Config
-import androidx.camera.core.impl.UseCaseConfigFactory
-
-/**
- * This class builds [Config] objects for a given [UseCaseConfigFactory.CaptureType].
- *
- * This includes things like default template and session parameters, as well as maximum resolution
- * and aspect ratios for the display.
- */
-class UseCaseConfigurationMap(context: Context) : UseCaseConfigFactory {
- init {
- if (context === context.applicationContext) {
- info {
- "The provided context ($context) is application scoped and will be used to infer " +
- "the default display for computing the default preview size, orientation, " +
- "and default aspect ratio for UseCase outputs."
- }
- }
- debug { "Created UseCaseConfigurationMap" }
- }
-
- /**
- * Returns the configuration for the given capture type, or `null` if the configuration
- * cannot be produced.
- */
- override fun getConfig(captureType: UseCaseConfigFactory.CaptureType): Config? {
- TODO("Not Implemented")
- }
-}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
new file mode 100644
index 0000000..85f4155
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2020 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.camera2.pipe.integration.impl
+
+import androidx.camera.camera2.pipe.impl.Log
+import androidx.camera.camera2.pipe.integration.config.CameraConfig
+import androidx.camera.camera2.pipe.integration.config.CameraScope
+import androidx.camera.camera2.pipe.integration.config.UseCaseCameraComponent
+import androidx.camera.camera2.pipe.integration.config.UseCaseCameraConfig
+import androidx.camera.core.UseCase
+import javax.inject.Inject
+
+/**
+ * This class keeps track of the currently attached and active [UseCase]'s for a specific camera.
+ */
+@CameraScope
+class UseCaseManager @Inject constructor(
+ private val cameraConfig: CameraConfig,
+ private val builder: UseCaseCameraComponent.Builder
+) {
+ private val attachedUseCases = mutableListOf<UseCase>()
+ private val enabledUseCases = mutableSetOf<UseCase>()
+
+ @Volatile
+ private var _activeComponent: UseCaseCameraComponent? = null
+ val camera: UseCaseCamera?
+ get() = _activeComponent?.getUseCaseCamera()
+
+ fun attach(useCases: List<UseCase>) {
+ if (useCases.isEmpty()) {
+ Log.warn { "Attach [] from $this (Ignored)" }
+ return
+ }
+ Log.debug { "Attaching $useCases from $this" }
+
+ var modified = false
+ for (useCase in useCases) {
+ if (!attachedUseCases.contains(useCase)) {
+ attachedUseCases.add(useCase)
+ modified = true
+ }
+ }
+
+ if (modified) {
+ start(attachedUseCases)
+ }
+ }
+
+ fun detach(useCases: List<UseCase>) {
+ if (useCases.isEmpty()) {
+ Log.warn { "Detaching [] from $this (Ignored)" }
+ return
+ }
+ Log.debug { "Detaching $useCases from $this" }
+
+ var modified = false
+ for (useCase in useCases) {
+ modified = attachedUseCases.remove(useCase) || modified
+ }
+
+ // TODO: We might only want to tear down when the number of attached use cases goes to
+ // zero. If a single UseCase is removed, we could deactivate it?
+ if (modified) {
+ start(attachedUseCases)
+ }
+ }
+
+ fun enable(useCase: UseCase) {
+ if (enabledUseCases.add(useCase)) {
+ invalidate()
+ }
+ }
+
+ fun disable(useCase: UseCase) {
+ if (enabledUseCases.remove(useCase)) {
+ invalidate()
+ }
+ }
+
+ fun update(useCase: UseCase) {
+ if (attachedUseCases.contains(useCase)) {
+ invalidate()
+ }
+ }
+
+ override fun toString(): String = "UseCaseManager<${cameraConfig.cameraId}>"
+
+ private fun invalidate() {
+ camera?.let {
+ it.activeUseCases = enabledUseCases.toSet()
+ }
+ }
+
+ private fun start(newUseCases: List<UseCase>) {
+ val useCases = newUseCases.toList()
+
+ // Close prior camera graph
+ camera.let {
+ _activeComponent = null
+ it?.close()
+ }
+
+ // Update list of active useCases
+ if (useCases.isEmpty()) {
+ return
+ }
+
+ // Create and configure the new camera component.
+ _activeComponent = builder.config(UseCaseCameraConfig(useCases)).build()
+ invalidate()
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
index c7bb3b0..d514661 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraPipe.kt
@@ -22,6 +22,9 @@
import androidx.camera.camera2.pipe.impl.CameraPipeComponent
import androidx.camera.camera2.pipe.impl.CameraPipeConfigModule
import androidx.camera.camera2.pipe.impl.DaggerCameraPipeComponent
+import kotlinx.atomicfu.atomic
+
+internal val cameraPipeIds = atomic(0)
/**
* [CameraPipe] is the top level scope for all interactions with a Camera2 camera.
@@ -33,6 +36,7 @@
* the [CameraGraph] interface.
*/
class CameraPipe(config: Config) {
+ private val debugId = cameraPipeIds.incrementAndGet()
private val component: CameraPipeComponent = DaggerCameraPipeComponent.builder()
.cameraPipeConfigModule(CameraPipeConfigModule(config))
.build()
@@ -63,4 +67,6 @@
val appContext: Context,
val cameraThread: HandlerThread? = null
)
+
+ override fun toString(): String = "CameraPipe-$debugId"
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
index 0c3cdda..f961304 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/Metadata.kt
@@ -47,7 +47,7 @@
* This will create a new Key instance, and will check to see that the key has not been
* previously created somewhere else.
*/
- internal fun <T> create(name: String): Key<T> {
+ fun <T> create(name: String): Key<T> {
synchronized(keys) {
check(keys.add(name)) { "$name is already defined!" }
}
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/RequestProcessor.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/RequestProcessor.kt
index 5204cc8..3a87a61 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/RequestProcessor.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/impl/RequestProcessor.kt
@@ -277,7 +277,7 @@
val surface = surfaceMap[stream]
if (surface != null) {
- Log.debug { " Binding $surface to $stream" }
+ Log.debug { " Binding $stream to $surface" }
// TODO(codelogic) There should be a more efficient way to do these lookups than
// having two maps.