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,
+)