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.