Implement stopRepeating() support and port current camera2 logic

This CL includes several changes meant to align CameraPipe's extensions
implementation with the camera-camera2's implementation. These include:

- Pass the original SessionConfig (not the updated one from
  SessionProcessor.initSession) down the stack.
- When setting the session configuration options, build a combined
  SessionConfig with setting updates such as zoom to SessionProcessor
  and RequestProcessor.
- Implement stopRepeating() by looking at the original SessionConfig,
  same as what camera-camera2 does.
- Add a state system in RequestProcessor such that it handles image
  capture requests before the capture session is started.

Bug: 320776624
Test: IamgeCaptureTest
Change-Id: Ic215d717c47d9871cd6ca64b298514783ca84537
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
index dea04ab..da840e51 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
@@ -149,6 +149,7 @@
                 streams = useCaseCameraGraphConfig.getStreamIdsFromSurfaces(
                     sessionConfig.repeatingCaptureConfig.surfaces
                 ),
+                sessionConfig = sessionConfig,
             )
         }
     }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapter.kt
index 86eda1a..8278652 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapter.kt
@@ -44,12 +44,12 @@
 @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
 class RequestProcessorAdapter(
     private val useCaseGraphConfig: UseCaseGraphConfig,
-    private val sessionConfig: SessionConfig?,
     private val processorSurfaces: List<SessionProcessorSurface>,
     private val scope: CoroutineScope,
 ) : RequestProcessor {
     private val coroutineMutex = CoroutineMutex()
     private val sequenceIds = atomic(0)
+    internal var sessionConfig: SessionConfig? = null
 
     private class RequestProcessorCallbackAdapter(
         private val callback: RequestProcessor.Callback,
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt
index 7ec7ec5..9e7daeb 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManager.kt
@@ -20,10 +20,12 @@
 import android.hardware.camera2.CaptureRequest
 import android.os.Build
 import android.view.Surface
+import androidx.annotation.GuardedBy
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraStream
 import androidx.camera.camera2.pipe.compat.CameraPipeKeys
 import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.integration.adapter.RequestProcessorAdapter
 import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
 import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
@@ -37,7 +39,6 @@
 import androidx.camera.core.impl.DeferrableSurfaces
 import androidx.camera.core.impl.OutputSurface
 import androidx.camera.core.impl.OutputSurfaceConfiguration
-import androidx.camera.core.impl.RequestProcessor
 import androidx.camera.core.impl.SessionConfig
 import androidx.camera.core.impl.SessionProcessor
 import androidx.camera.core.impl.SessionProcessor.CaptureCallback
@@ -57,11 +58,32 @@
     private val cameraInfoInternal: CameraInfoInternal,
     private val scope: CoroutineScope,
 ) {
+    private val lock = Any()
+
+    @GuardedBy("lock")
+    private var captureSessionStarted = false
+
+    @GuardedBy("lock")
     private var sessionOptions = CaptureRequestOptions.Builder().build()
+
+    @GuardedBy("lock")
     private var stillCaptureOptions = CaptureRequestOptions.Builder().build()
+
+    @GuardedBy("lock")
+    private var requestProcessor: RequestProcessorAdapter? = null
+
+    @GuardedBy("lock")
+    private var pendingCaptureConfigs: List<CaptureConfig>? = null
+
+    @GuardedBy("lock")
+    private var pendingCaptureCallbacks: List<CaptureCallback>? = null
+
+    @GuardedBy("lock")
     internal var sessionConfig: SessionConfig? = null
-        set(value) {
+        set(value) = synchronized(lock) {
             field = checkNotNull(value)
+            if (!captureSessionStarted) return
+            checkNotNull(requestProcessor).sessionConfig = value
             sessionOptions =
                 CaptureRequestOptions.Builder.from(value.implementationOptions).build()
             updateOptions()
@@ -170,20 +192,56 @@
         useCaseManager.tryResumeUseCaseManager(useCaseManagerConfig)
     }
 
-    internal fun onCaptureSessionStart(requestProcessor: RequestProcessor) {
+    internal fun onCaptureSessionStart(requestProcessor: RequestProcessorAdapter) {
+        var captureConfigsToIssue: List<CaptureConfig>?
+        var captureCallbacksToIssue: List<CaptureCallback>?
+        synchronized(lock) {
+            check(!captureSessionStarted)
+            requestProcessor.sessionConfig = sessionConfig
+            this.requestProcessor = requestProcessor
+            captureSessionStarted = true
+
+            captureConfigsToIssue = pendingCaptureConfigs
+            captureCallbacksToIssue = pendingCaptureCallbacks
+            pendingCaptureConfigs = null
+            pendingCaptureCallbacks = null
+        }
+        Log.debug { "Invoking SessionProcessor#onCaptureSessionStart" }
         sessionProcessor.onCaptureSessionStart(requestProcessor)
         startRepeating(object : CaptureCallback {})
+        captureConfigsToIssue?.let { captureConfigs ->
+            submitCaptureConfigs(captureConfigs, checkNotNull(captureCallbacksToIssue))
+        }
     }
 
     internal fun startRepeating(captureCallback: CaptureCallback) {
+        synchronized(lock) {
+            if (!captureSessionStarted) return
+        }
+        Log.debug { "Invoking SessionProcessor#startRepeating" }
         sessionProcessor.startRepeating(captureCallback)
     }
 
+    internal fun stopRepeating() {
+        synchronized(lock) {
+            if (!captureSessionStarted) return
+        }
+        Log.debug { "Invoking SessionProcessor#stopRepeating" }
+        sessionProcessor.stopRepeating()
+    }
+
     internal fun submitCaptureConfigs(
         captureConfigs: List<CaptureConfig>,
         captureCallbacks: List<CaptureCallback>,
     ) {
         check(captureConfigs.size == captureCallbacks.size)
+        synchronized(lock) {
+            if (!captureSessionStarted) {
+                pendingCaptureConfigs = captureConfigs
+                pendingCaptureCallbacks = captureCallbacks
+                return
+            }
+        }
         for ((config, callback) in captureConfigs.zip(captureCallbacks)) {
             if (config.templateType == CameraDevice.TEMPLATE_STILL_CAPTURE) {
                 val builder = CaptureRequestOptions.Builder.from(config.implementationOptions)
@@ -204,8 +262,10 @@
                         )!!.toByte()
                     )
                 }
-                stillCaptureOptions = builder.build()
-                updateOptions()
+                synchronized(lock) {
+                    stillCaptureOptions = builder.build()
+                    updateOptions()
+                }
                 Log.debug { "Invoking SessionProcessor.startCapture()" }
                 sessionProcessor.startCapture(config.isPostviewEnabled, callback)
             } else {
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 d43c348..bd284dd 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
@@ -104,13 +104,6 @@
         set(value) {
             field = value
 
-            if (sessionProcessorManager != null) {
-                sessionConfigAdapter.getValidSessionConfigOrNull()?.let {
-                    requestControl.setSessionConfigAsync(it)
-                }
-                return
-            }
-
             // 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.
             SessionConfigAdapter(value).getValidSessionConfigOrNull()?.let {
@@ -158,7 +151,6 @@
                             }
                         val requestProcessorAdapter = RequestProcessorAdapter(
                             useCaseGraphConfig,
-                            sessionConfigAdapter.getValidSessionConfigOrNull(),
                             sessionProcessorSurfaces,
                             threads.scope,
                         )
@@ -225,6 +217,7 @@
             streams = useCaseGraphConfig.getStreamIdsFromSurfaces(
                 sessionConfig.repeatingCaptureConfig.surfaces
             ),
+            sessionConfig = sessionConfig,
         )
     } ?: canceledResult
 
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
index a0c8e87..abe5af1 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
@@ -41,6 +41,7 @@
 import androidx.camera.core.impl.CaptureConfig.TEMPLATE_TYPE_NONE
 import androidx.camera.core.impl.Config
 import androidx.camera.core.impl.MutableTagBundle
+import androidx.camera.core.impl.SessionConfig
 import androidx.camera.core.impl.TagBundle
 import dagger.Binds
 import dagger.Module
@@ -125,7 +126,8 @@
         tags: Map<String, Any> = emptyMap(),
         streams: Set<StreamId>? = null,
         template: RequestTemplate? = null,
-        listeners: Set<Request.Listener> = emptySet()
+        listeners: Set<Request.Listener> = emptySet(),
+        sessionConfig: SessionConfig? = null,
     ): Deferred<Unit>
 
     // 3A
@@ -200,7 +202,8 @@
         tags: Map<String, Any>,
         streams: Set<StreamId>?,
         template: RequestTemplate?,
-        listeners: Set<Request.Listener>
+        listeners: Set<Request.Listener>,
+        sessionConfig: SessionConfig?,
     ): Deferred<Unit> = runIfNotClosed {
         synchronized(lock) {
             debug { "[$type] Set config: ${config?.toParameters()}" }
@@ -217,6 +220,7 @@
             infoBundleMap.merge()
         }.updateCameraStateAsync(
             streams = streams,
+            sessionConfig = sessionConfig,
         )
     } ?: canceledResult
 
@@ -352,7 +356,10 @@
             }
         }
 
-    private fun InfoBundle.updateCameraStateAsync(streams: Set<StreamId>? = null): Deferred<Unit> =
+    private fun InfoBundle.updateCameraStateAsync(
+        streams: Set<StreamId>? = null,
+        sessionConfig: SessionConfig? = null,
+    ): Deferred<Unit> =
         runIfNotClosed {
             capturePipeline.template =
                 if (template != null && template!!.value != TEMPLATE_TYPE_NONE) {
@@ -369,6 +376,7 @@
                 streams = streams,
                 template = template,
                 listeners = listeners,
+                sessionConfig = sessionConfig,
             )
         } ?: canceledResult
 
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 0737345..2fe0e04 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
@@ -36,9 +36,11 @@
 import androidx.camera.camera2.pipe.core.Log
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
+import androidx.camera.core.Preview
 import androidx.camera.core.impl.SessionConfig
 import androidx.camera.core.impl.SessionProcessor.CaptureCallback
 import androidx.camera.core.impl.TagBundle
+import androidx.camera.core.streamsharing.StreamSharing
 import javax.inject.Inject
 import kotlinx.atomicfu.atomic
 import kotlinx.coroutines.CancellationException
@@ -96,6 +98,9 @@
     @GuardedBy("lock")
     private var currentTemplate: RequestTemplate? = null
 
+    @GuardedBy("lock")
+    private var currentSessionConfig: SessionConfig? = null
+
     private val requestListener = RequestListener()
 
     /**
@@ -119,6 +124,7 @@
         streams: Set<StreamId>? = null,
         template: RequestTemplate? = null,
         listeners: Set<Request.Listener>? = null,
+        sessionConfig: SessionConfig? = null,
     ): Deferred<Unit> {
         val result: Deferred<Unit>
         synchronized(lock) {
@@ -139,7 +145,7 @@
             updateState(
                 parameters, appendParameters, internalParameters,
                 appendInternalParameters, streams, template,
-                listeners
+                listeners, sessionConfig
             )
 
             if (updateSignal == null) {
@@ -198,7 +204,8 @@
         appendInternalParameters: Boolean = true,
         streams: Set<StreamId>? = null,
         template: RequestTemplate? = null,
-        listeners: Set<Request.Listener>? = null
+        listeners: Set<Request.Listener>? = null,
+        sessionConfig: SessionConfig? = null,
     ) {
         // TODO: Consider if this should detect changes and only invoke an update if state has
         //  actually changed.
@@ -226,6 +233,9 @@
             currentListeners.clear()
             currentListeners.addAll(listeners)
         }
+        if (sessionConfig != null) {
+            currentSessionConfig = sessionConfig
+        }
     }
 
     /**
@@ -235,6 +245,11 @@
     fun tryStartRepeating() = submitLatest()
 
     private fun submitLatest() {
+        if (sessionProcessorManager != null) {
+            submitLatestWithSessionProcessor()
+            return
+        }
+
         // Update the cameraGraph with the most recent set of values.
         // Since acquireSession is a suspending function, it's possible that subsequent updates
         // can occur while waiting for the acquireSession call to complete. If this happens,
@@ -242,7 +257,6 @@
         // synchronously with the latest values. The startRepeating/stopRepeating call happens
         // outside of the synchronized block to avoid holding a lock while updating the camera
         // state.
-
         threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
             val result: CompletableDeferred<Unit>?
             val request: Request?
@@ -285,26 +299,7 @@
                             }
                         }
                         Log.debug { "Update RepeatingRequest: $request" }
-                        if (sessionProcessorManager != null) {
-                            val sessionConfig = SessionConfig.Builder().apply {
-                                request.template?.let { setTemplateType(it.value) }
-                                setImplementationOptions(Camera2ImplConfig.Builder().apply {
-                                    for ((key, value) in request.parameters) {
-                                        setCaptureRequestOptionWithType(key, value)
-                                    }
-                                }.build())
-                                currentInternalParameters[CAMERAX_TAG_BUNDLE]?.let {
-                                    val tagBundleMap = (it as TagBundle).toMap()
-                                    for ((tag, value) in tagBundleMap) {
-                                        addTag(tag, value)
-                                    }
-                                }
-                            }.build()
-                            sessionProcessorManager.sessionConfig = sessionConfig
-                            sessionProcessorManager.startRepeating(object : CaptureCallback {})
-                        } else {
-                            it.startRepeating(request)
-                        }
+                        it.startRepeating(request)
                         it.update3A(request.parameters)
                     }
                 }
@@ -320,6 +315,52 @@
         }
     }
 
+    private fun submitLatestWithSessionProcessor() {
+        checkNotNull(sessionProcessorManager)
+        synchronized(lock) {
+            updating = false
+            val signal = updateSignal
+            updateSignal = null
+
+            if (currentSessionConfig == null) {
+                signal?.complete(Unit)
+                return
+            }
+
+            // Here we're intentionally building a new SessionConfig. Various request parameters,
+            // such as zoom or 3A are directly translated to corresponding CameraPipe types and
+            // APIs. As such, we need to build a new, "combined" SessionConfig that has these
+            // updated request parameters set. Otherwise, certain settings like zoom would be
+            // disregarded.
+            SessionConfig.Builder().apply {
+                currentTemplate?.let { setTemplateType(it.value) }
+                setImplementationOptions(Camera2ImplConfig.Builder().apply {
+                    for ((key, value) in currentParameters) {
+                        setCaptureRequestOptionWithType(key, value)
+                    }
+                }.build())
+                currentInternalParameters[CAMERAX_TAG_BUNDLE]?.let {
+                    val tagBundleMap = (it as TagBundle).toMap()
+                    for ((tag, value) in tagBundleMap) {
+                        addTag(tag, value)
+                    }
+                }
+            }.build().also { sessionConfig ->
+                sessionProcessorManager.sessionConfig = sessionConfig
+            }
+
+            if (currentSessionConfig!!.repeatingCaptureConfig.surfaces.any {
+                    it.containerClass == Preview::class.java ||
+                        it.containerClass == StreamSharing::class.java
+                }) {
+                sessionProcessorManager.startRepeating(object : CaptureCallback {})
+            } else {
+                sessionProcessorManager.stopRepeating()
+            }
+            signal?.complete(Unit)
+        }
+    }
+
     private fun CameraGraph.Session.update3A(parameters: Map<CaptureRequest.Key<*>, Any>?) {
         val aeMode = parameters.getIntOrNull(CaptureRequest.CONTROL_AE_MODE)?.let {
             AeMode.fromIntOrNull(it)
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
index f8c2a8e..1cc2d4a5 100644
--- 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
@@ -123,14 +123,6 @@
         }
         set(value) = synchronized(lock) {
             field = value
-            // Only create the SessionProcessorManager when we have a SessionProcessor set.
-            if (field != null) {
-                sessionProcessorManager = SessionProcessorManager(
-                    field!!,
-                    cameraInfoInternal.get(),
-                    useCaseThreads.get().scope
-                )
-            }
         }
 
     @GuardedBy("lock")
@@ -349,7 +341,13 @@
 
         if (sessionProcessor != null) {
             Log.debug { "Setting up UseCaseManager with SessionProcessorManager" }
-            checkNotNull(sessionProcessorManager).initialize(this, useCases)
+            sessionProcessorManager = SessionProcessorManager(
+                sessionProcessor!!,
+                cameraInfoInternal.get(),
+                useCaseThreads.get().scope,
+            ).also {
+                it.initialize(this, useCases)
+            }
             return
         } else {
             val sessionConfigAdapter = SessionConfigAdapter(useCases)
@@ -388,13 +386,7 @@
         val sessionProcessorEnabled =
             useCaseManagerConfig.sessionConfigAdapter.isSessionProcessorEnabled
         with(useCaseManagerConfig) {
-            var sessionProcessorManager: SessionProcessorManager? = null
             if (sessionProcessorEnabled) {
-                sessionProcessorManager = SessionProcessorManager(
-                    checkNotNull(sessionProcessor),
-                    cameraInfoInternal.get(),
-                    useCaseThreads.get().scope,
-                )
                 for ((streamConfig, deferrableSurface) in streamConfigMap) {
                     cameraGraph.streams[streamConfig]?.let {
                         cameraGraph.setSurface(it.id, deferrableSurface.surface.get())
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapterTest.kt
index 4b86a82..bde75b0 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/RequestProcessorAdapterTest.kt
@@ -132,10 +132,11 @@
 
         requestProcessorAdapter = RequestProcessorAdapter(
             useCaseGraphConfig,
-            fakeSessionConfig,
             sessionProcessorSurfaces,
             scope,
-        )
+        ).apply {
+            sessionConfig = fakeSessionConfig
+        }
         scope.advanceUntilIdle()
     }
 
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManagerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManagerTest.kt
index 1e506bc..db12111 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManagerTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/SessionProcessorManagerTest.kt
@@ -23,6 +23,7 @@
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.core.Log
 import androidx.camera.camera2.pipe.integration.adapter.FakeTestUseCase
+import androidx.camera.camera2.pipe.integration.adapter.RequestProcessorAdapter
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
 import androidx.camera.camera2.pipe.integration.adapter.TestDeferrableSurface
 import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions
@@ -120,7 +121,7 @@
         }
 
         override fun onCaptureSessionStart(requestProcessor: RequestProcessor) {
-            TODO("Not yet implemented")
+            Log.debug { "$this#onCaptureSessionStart" }
         }
 
         override fun onCaptureSessionEnd() {
@@ -128,11 +129,12 @@
         }
 
         override fun startRepeating(callback: CaptureCallback): Int {
-            TODO("Not yet implemented")
+            Log.debug { "$this#startRepeating" }
+            return 0
         }
 
         override fun stopRepeating() {
-            TODO("Not yet implemented")
+            Log.debug { "$this#stopRepeating" }
         }
 
         override fun startCapture(
@@ -229,6 +231,9 @@
         ).join()
         sessionProcessorManager.sessionConfig = SessionConfig.Builder().build()
 
+        val mockRequestProcessorAdapter: RequestProcessorAdapter = mock()
+        sessionProcessorManager.onCaptureSessionStart(mockRequestProcessorAdapter)
+
         val jpegRotation = 90
         val jpegQuality = 95
         val captureConfig = CaptureConfig.Builder().apply {
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
index 3f60f25..bb21a18 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
@@ -40,6 +40,7 @@
 import androidx.camera.core.impl.CaptureConfig
 import androidx.camera.core.impl.Config
 import androidx.camera.core.impl.DeferrableSurface
+import androidx.camera.core.impl.SessionConfig
 import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.Deferred
@@ -110,7 +111,8 @@
         tags: Map<String, Any>,
         streams: Set<StreamId>?,
         template: RequestTemplate?,
-        listeners: Set<Request.Listener>
+        listeners: Set<Request.Listener>,
+        sessionConfig: SessionConfig?,
     ): Deferred<Unit> {
         setConfigCalls.add(RequestParameters(type, config, tags))
         return CompletableDeferred(Unit)