Don't block the calling thread waiting for surface

This fixes an issue where the calling thread could be blocked waiting
for the preview surface to be available.

Bug: 182719595

Test: ./gradlew\
 :camera:camera-camera2-pipe:testDebugUnitTest\
 :camera:camera-camera2-pipe-testing:testDebugUnitTest\
 :camera:camera-camera2-pipe-integration:testDebugUnitTest\
 -Pandroidx.allWarningsAsErrors
Test: adb shell am start -n androidx.camera.integration.core/.CameraXActivity --es "camera_implementation" "camera_pipe"

Change-Id: Iaf416bdfe62b36b944e176b110bf7abcc9bf8ca5
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
index c2e6423..02ad9bb 100644
--- 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
@@ -22,6 +22,7 @@
 import androidx.camera.camera2.pipe.CameraPipe
 import androidx.camera.camera2.pipe.core.Log.warn
 import androidx.camera.camera2.pipe.integration.config.CameraScope
+import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
 import androidx.camera.camera2.pipe.integration.impl.CameraProperties
 import androidx.camera.camera2.pipe.integration.impl.EvCompControl
 import androidx.camera.camera2.pipe.integration.impl.UseCaseCamera
@@ -38,7 +39,6 @@
 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.ExperimentalCoroutinesApi
 import kotlinx.coroutines.async
@@ -57,7 +57,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class CameraControlAdapter @Inject constructor(
     private val cameraProperties: CameraProperties,
-    private val cameraScope: CoroutineScope,
+    private val threads: UseCaseThreads,
     private val useCaseManager: UseCaseManager,
     private val cameraStateAdapter: CameraStateAdapter,
     private val zoomControl: ZoomControl,
@@ -84,7 +84,7 @@
 
     override fun enableTorch(torch: Boolean): ListenableFuture<Void> {
         // Launch UNDISPATCHED to preserve interaction order with the camera.
-        return cameraScope.launch(start = CoroutineStart.UNDISPATCHED) {
+        return threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
             useCaseManager.camera?.let {
                 // Tell the camera to turn the torch on / off.
                 val result = it.setTorchAsync(torch)
@@ -116,7 +116,7 @@
     }
 
     override fun setZoomRatio(ratio: Float): ListenableFuture<Void> {
-        return cameraScope.launch(start = CoroutineStart.UNDISPATCHED) {
+        return threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
             useCaseManager.camera?.let {
                 zoomControl.zoomRatio = ratio
                 val zoomValue = ZoomValue(
@@ -159,7 +159,7 @@
 
     @SuppressLint("UnsafeExperimentalUsageError")
     override fun setExposureCompensationIndex(exposure: Int): ListenableFuture<Int> {
-        return cameraScope.async(start = CoroutineStart.UNDISPATCHED) {
+        return threads.scope.async(start = CoroutineStart.UNDISPATCHED) {
             useCaseManager.camera?.let {
                 evCompControl.evCompIndex = exposure
                 cameraStateAdapter.setExposureState(
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
index d8eadf0..3314bcc 100644
--- 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
@@ -48,10 +48,13 @@
 @Module
 class CameraAppConfig(
     private val context: Context,
-    private val threadConfig: CameraThreadConfig
+    private val cameraThreadConfig: CameraThreadConfig
 ) {
     @Provides
     fun provideContext(): Context = context
+
+    @Provides
+    fun provideCameraThreadConfig(): CameraThreadConfig = cameraThreadConfig
 }
 
 /** Dagger component for Application (Process) scoped dependencies. */
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
index 0953944..028bc66 100644
--- 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
@@ -27,18 +27,20 @@
 import androidx.camera.camera2.pipe.integration.impl.CameraProperties
 import androidx.camera.camera2.pipe.integration.compat.ZoomCompat
 import androidx.camera.camera2.pipe.integration.impl.EvCompControl
+import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
 import androidx.camera.camera2.pipe.integration.impl.ZoomControl
 import androidx.camera.core.impl.CameraControlInternal
 import androidx.camera.core.impl.CameraInfoInternal
 import androidx.camera.core.impl.CameraInternal
+import androidx.camera.core.impl.CameraThreadConfig
 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 kotlinx.coroutines.asCoroutineDispatcher
 import javax.inject.Scope
 
 @Scope
@@ -56,16 +58,28 @@
 )
 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(
+        fun provideCameraThreadConfig(
+            cameraConfig: CameraConfig,
+            cameraThreadConfig: CameraThreadConfig
+        ): UseCaseThreads {
+
+            val executor = cameraThreadConfig.cameraExecutor
+            val dispatcher = cameraThreadConfig.cameraExecutor.asCoroutineDispatcher()
+
+            val cameraScope = CoroutineScope(
                 Job() +
-                    Dispatchers.Default +
+                    dispatcher +
                     CoroutineName("CXCP-Camera-${cameraConfig.cameraId.value}")
             )
+
+            return UseCaseThreads(
+                cameraScope,
+                executor,
+                dispatcher
+            )
         }
 
         @Provides
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
index 082989e..e16c2d3 100644
--- 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
@@ -17,6 +17,7 @@
 package androidx.camera.camera2.pipe.integration.impl
 
 import android.hardware.camera2.CaptureRequest
+import android.view.Surface
 import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.CameraPipe
 import androidx.camera.camera2.pipe.CameraStream
@@ -29,10 +30,11 @@
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
 import androidx.camera.core.UseCase
 import androidx.camera.core.impl.DeferrableSurface
+import androidx.camera.core.impl.utils.futures.FutureCallback
+import androidx.camera.core.impl.utils.futures.Futures
 import dagger.Module
 import dagger.Provides
 import kotlinx.atomicfu.atomic
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Deferred
 
 internal val useCaseCameraIds = atomic(0)
@@ -147,7 +149,7 @@
                 useCases: java.util.ArrayList<UseCase>,
                 cameraConfig: CameraConfig,
                 callbackMap: CameraCallbackMap,
-                coroutineScope: CoroutineScope,
+                threads: UseCaseThreads,
             ): UseCaseCamera {
                 val streamConfigs = mutableListOf<CameraStream.Config>()
                 val useCaseMap = mutableMapOf<CameraStream.Config, UseCase>()
@@ -184,12 +186,27 @@
                     val deferredSurfaces = useCaseSessionConfig?.surfaces
                     if (stream != null && deferredSurfaces != null && deferredSurfaces.size == 1) {
                         val deferredSurface = deferredSurfaces.first()
-                        graph.setSurface(stream.id, deferredSurface.surface.get())
                         surfaceToStreamMap[deferredSurface] = stream.id
+
+                        Futures.addCallback(
+                            deferredSurface.surface,
+                            object : FutureCallback<Surface?> {
+                                override fun onSuccess(result: Surface?) {
+                                    debug { "Configured $result for $stream" }
+                                    graph.setSurface(stream.id, result)
+                                }
+
+                                override fun onFailure(t: Throwable) {
+                                    debug(t) { "Surface for $deferredSurface failed to arrive!" }
+                                    graph.setSurface(stream.id, null)
+                                }
+                            },
+                            threads.backgroundExecutor
+                        )
                     }
                 }
 
-                val state = UseCaseCameraState(graph, coroutineScope)
+                val state = UseCaseCameraState(graph, threads)
 
                 graph.start()
                 return UseCaseCameraImpl(graph, useCases, surfaceToStreamMap, state)
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
index 1520d65..b1cb05d 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
@@ -27,7 +27,6 @@
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
 import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -46,7 +45,7 @@
 @UseCaseCameraScope
 class UseCaseCameraState @Inject constructor(
     private val cameraGraph: CameraGraph,
-    private val coroutineScope: CoroutineScope
+    private val threads: UseCaseThreads
 ) {
     private val lock = Any()
 
@@ -158,7 +157,7 @@
         // synchronously with the latest values. The setRepeating call happens outside of the
         // synchronized block to avoid holding a lock while updating the camera state.
 
-        coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
+        threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
             val result: CompletableDeferred<Unit>?
             cameraGraph.acquireSession().use {
                 val request: Request
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseThreads.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseThreads.kt
new file mode 100644
index 0000000..c96c461
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseThreads.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2021 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 kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import java.util.concurrent.Executor
+
+/**
+ * Collection of threads and scope(s) that have been configured and tuned.
+ */
+class UseCaseThreads(
+    val scope: CoroutineScope,
+
+    val backgroundExecutor: Executor,
+    val backgroundDispatcher: CoroutineDispatcher,
+)