Merge "Adapt GraphState to CameraState" into androidx-main
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 8f93913..657c698 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
@@ -28,6 +28,7 @@
import androidx.camera.camera2.pipe.CameraPipe
import androidx.camera.camera2.pipe.Request
import androidx.camera.camera2.pipe.Result3A
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter
import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
import androidx.camera.camera2.pipe.integration.config.CameraConfig
@@ -64,13 +65,14 @@
cameraPipe,
),
) : UseCaseCamera {
- val useCaseCameraGraphConfig = UseCaseCameraConfig(useCases).provideUseCaseGraphConfig(
- callbackMap = callbackMap,
- cameraConfig = cameraConfig,
- cameraPipe = cameraPipe,
- requestListener = ComboRequestListener(),
- useCaseSurfaceManager = useCaseSurfaceManager,
- )
+ val useCaseCameraGraphConfig =
+ UseCaseCameraConfig(useCases, CameraStateAdapter()).provideUseCaseGraphConfig(
+ callbackMap = callbackMap,
+ cameraConfig = cameraConfig,
+ cameraPipe = cameraPipe,
+ requestListener = ComboRequestListener(),
+ useCaseSurfaceManager = useCaseSurfaceManager,
+ )
override val requestControl: UseCaseCameraRequestControl = UseCaseCameraRequestControlImpl(
configAdapter = CaptureConfigAdapter(
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 c3b6ccd..91b9fe9 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
@@ -67,7 +67,7 @@
@OptIn(ExperimentalCoroutinesApi::class, ExperimentalCamera2Interop::class)
class CameraControlAdapter @Inject constructor(
private val cameraProperties: CameraProperties,
- private val cameraStateAdapter: CameraStateAdapter,
+ private val cameraControlStateAdapter: CameraControlStateAdapter,
private val evCompControl: EvCompControl,
private val flashControl: FlashControl,
private val torchControl: TorchControl,
@@ -131,7 +131,7 @@
zoomControl.minZoom,
zoomControl.maxZoom
)
- cameraStateAdapter.setZoomState(zoomValue)
+ cameraControlStateAdapter.setZoomState(zoomValue)
}
}.asListenableFuture()
}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlStateAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlStateAdapter.kt
new file mode 100644
index 0000000..6e3e98c
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlStateAdapter.kt
@@ -0,0 +1,67 @@
+/*
+ * 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 androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.integration.config.CameraScope
+import androidx.camera.camera2.pipe.integration.impl.EvCompControl
+import androidx.camera.camera2.pipe.integration.impl.TorchControl
+import androidx.camera.camera2.pipe.integration.impl.ZoomControl
+import androidx.camera.core.ExposureState
+import androidx.camera.core.ZoomState
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import javax.inject.Inject
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+/**
+ * [CameraControlStateAdapter] caches and updates based on callbacks from the active CameraGraph.
+ */
+@SuppressLint("UnsafeOptInUsageError")
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@CameraScope
+class CameraControlStateAdapter @Inject constructor(
+ private val zoomControl: ZoomControl,
+ private val evCompControl: EvCompControl,
+ private val torchControl: TorchControl,
+) {
+ val torchStateLiveData: LiveData<Int>
+ get() = torchControl.torchStateLiveData
+
+ private val _zoomState by lazy {
+ MutableLiveData<ZoomState>(
+ ZoomValue(
+ zoomControl.zoomRatio,
+ zoomControl.minZoom,
+ zoomControl.maxZoom
+ )
+ )
+ }
+ val zoomStateLiveData: LiveData<ZoomState>
+ get() = _zoomState
+
+ suspend fun setZoomState(value: ZoomState) {
+ withContext(Dispatchers.Main) {
+ _zoomState.value = value
+ }
+ }
+
+ val exposureState: ExposureState
+ get() = evCompControl.exposureState
+}
\ 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
index 3f8b947..2f49b0ac 100644
--- 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
@@ -43,7 +43,6 @@
import androidx.camera.core.impl.Timebase
import androidx.camera.core.impl.utils.CameraOrientationUtil
import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -59,7 +58,8 @@
class CameraInfoAdapter @Inject constructor(
private val cameraProperties: CameraProperties,
private val cameraConfig: CameraConfig,
- private val cameraState: CameraStateAdapter,
+ private val cameraStateAdapter: CameraStateAdapter,
+ private val cameraControlStateAdapter: CameraControlStateAdapter,
private val cameraCallbackMap: CameraCallbackMap,
) : CameraInfoInternal {
private lateinit var camcorderProfileProviderAdapter: CamcorderProfileProviderAdapter
@@ -94,16 +94,13 @@
)
}
- override fun getZoomState(): LiveData<ZoomState> = cameraState.zoomStateLiveData
- override fun getTorchState(): LiveData<Int> = cameraState.torchStateLiveData
+ override fun getZoomState(): LiveData<ZoomState> = cameraControlStateAdapter.zoomStateLiveData
+ override fun getTorchState(): LiveData<Int> = cameraControlStateAdapter.torchStateLiveData
@SuppressLint("UnsafeOptInUsageError")
- override fun getExposureState(): ExposureState = cameraState.exposureState
+ override fun getExposureState(): ExposureState = cameraControlStateAdapter.exposureState
- override fun getCameraState(): LiveData<CameraState> {
- Log.warn { "TODO: CameraState is not yet supported." }
- return MutableLiveData(CameraState.create(CameraState.Type.CLOSED))
- }
+ override fun getCameraState(): LiveData<CameraState> = cameraStateAdapter.cameraState
override fun addSessionCaptureCallback(executor: Executor, callback: CameraCaptureCallback) =
cameraCallbackMap.addCaptureCallback(callback, executor)
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
index b4bcd62..c929ca0 100644
--- 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
@@ -30,7 +30,6 @@
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 com.google.common.util.concurrent.ListenableFuture
import javax.inject.Inject
@@ -49,16 +48,14 @@
private val cameraInfo: CameraInfoInternal,
private val cameraController: CameraControlInternal,
private val threads: UseCaseThreads,
+ private val cameraStateAdapter: CameraStateAdapter
) : CameraInternal {
private val cameraId = config.cameraId
private var coreCameraConfig: androidx.camera.core.impl.CameraConfig =
CameraConfigs.emptyConfig()
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.
}
@@ -78,7 +75,9 @@
}
override fun getCameraInfoInternal(): CameraInfoInternal = cameraInfo
- override fun getCameraState(): Observable<CameraInternal.State> = cameraState
+ override fun getCameraState(): Observable<CameraInternal.State> =
+ cameraStateAdapter.cameraInternalState
+
override fun getCameraControlInternal(): CameraControlInternal = cameraController
// UseCase attach / detach behaviors.
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraStateAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraStateAdapter.kt
index e4cc398..28de2a6 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraStateAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraStateAdapter.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Android Open Source Project
+ * Copyright 2022 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.
@@ -16,51 +16,128 @@
package androidx.camera.camera2.pipe.integration.adapter
-import android.annotation.SuppressLint
+import android.os.Looper
+import androidx.annotation.GuardedBy
import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.GraphState
+import androidx.camera.camera2.pipe.GraphState.GraphStateStarted
+import androidx.camera.camera2.pipe.GraphState.GraphStateStarting
+import androidx.camera.camera2.pipe.GraphState.GraphStateStopped
+import androidx.camera.camera2.pipe.GraphState.GraphStateStopping
+import androidx.camera.camera2.pipe.core.Log
import androidx.camera.camera2.pipe.integration.config.CameraScope
-import androidx.camera.camera2.pipe.integration.impl.EvCompControl
-import androidx.camera.camera2.pipe.integration.impl.TorchControl
-import androidx.camera.camera2.pipe.integration.impl.ZoomControl
-import androidx.camera.core.ExposureState
-import androidx.camera.core.ZoomState
-import androidx.lifecycle.LiveData
+import androidx.camera.core.CameraState
+import androidx.camera.core.impl.CameraInternal
+import androidx.camera.core.impl.LiveDataObservable
import androidx.lifecycle.MutableLiveData
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
import javax.inject.Inject
-/**
- * [CameraStateAdapter] caches and updates based on callbacks from the active CameraGraph.
- */
-@SuppressLint("UnsafeOptInUsageError")
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
@CameraScope
-class CameraStateAdapter @Inject constructor(
- private val zoomControl: ZoomControl,
- private val evCompControl: EvCompControl,
- private val torchControl: TorchControl,
-) {
- val torchStateLiveData: LiveData<Int>
- get() = torchControl.torchStateLiveData
+@RequiresApi(21)
+class CameraStateAdapter @Inject constructor() {
+ private val lock = Any()
- private val _zoomState by lazy {
- MutableLiveData<ZoomState>(
- ZoomValue(
- zoomControl.zoomRatio,
- zoomControl.minZoom,
- zoomControl.maxZoom
- )
- )
+ internal val cameraInternalState = LiveDataObservable<CameraInternal.State>()
+ internal val cameraState = MutableLiveData<CameraState>()
+
+ @GuardedBy("lock")
+ private var currentGraph: CameraGraph? = null
+
+ @GuardedBy("lock")
+ private var currentGraphState: GraphState = GraphStateStopped
+
+ init {
+ postCameraState(CameraInternal.State.CLOSED)
}
- val zoomStateLiveData: LiveData<ZoomState>
- get() = _zoomState
- suspend fun setZoomState(value: ZoomState) {
- withContext(Dispatchers.Main) {
- _zoomState.value = value
+
+ public fun onGraphUpdated(cameraGraph: CameraGraph) = synchronized(lock) {
+ Log.debug { "Camera graph updated from $currentGraph to $cameraGraph" }
+ if (currentGraphState != GraphStateStopped) {
+ postCameraState(CameraInternal.State.CLOSING)
+ postCameraState(CameraInternal.State.CLOSED)
+ }
+ currentGraph = cameraGraph
+ currentGraphState = GraphStateStopped
+ }
+
+ public fun onGraphStateUpdated(cameraGraph: CameraGraph, graphState: GraphState) =
+ synchronized(lock) {
+ Log.debug { "$cameraGraph state updated to $graphState" }
+ handleStateTransition(cameraGraph, graphState)
+ }
+
+ @GuardedBy("lock")
+ private fun handleStateTransition(cameraGraph: CameraGraph, graphState: GraphState) {
+ // If the transition came from a different camera graph, consider it stale and ignore it.
+ if (cameraGraph != currentGraph) {
+ Log.debug { "Ignored stale transition $graphState for $cameraGraph" }
+ return
+ }
+
+ if (!isTransitionPermissible(currentGraphState, graphState)) {
+ Log.warn { "Impermissible state transition from $currentGraphState to $graphState" }
+ return
+ }
+ currentGraphState = graphState
+
+ // Now that the current graph state is updated, post the latest states.
+ Log.debug { "Updated current graph state to $currentGraphState" }
+ postCameraState(currentGraphState.toCameraInternalState())
+ }
+
+ private fun postCameraState(internalState: CameraInternal.State) {
+ cameraInternalState.postValue(internalState)
+ cameraState.setOrPostValue(CameraState.create(internalState.toCameraState()))
+ }
+
+ private fun isTransitionPermissible(oldState: GraphState, newState: GraphState): Boolean {
+ return when (oldState) {
+ GraphStateStarting ->
+ newState == GraphStateStarted ||
+ newState == GraphStateStopping ||
+ newState == GraphStateStopped
+
+ GraphStateStarted ->
+ newState == GraphStateStopping ||
+ newState == GraphStateStopped
+
+ GraphStateStopping ->
+ newState == GraphStateStopped ||
+ newState == GraphStateStarting
+
+ GraphStateStopped ->
+ newState == GraphStateStarting ||
+ newState == GraphStateStarted
+
+ else -> false
}
}
+}
- val exposureState: ExposureState
- get() = evCompControl.exposureState
+@RequiresApi(21)
+internal fun GraphState.toCameraInternalState(): CameraInternal.State = when (this) {
+ GraphStateStarting -> CameraInternal.State.OPENING
+ GraphStateStarted -> CameraInternal.State.OPEN
+ GraphStateStopping -> CameraInternal.State.CLOSING
+ GraphStateStopped -> CameraInternal.State.CLOSED
+ else -> throw IllegalArgumentException("Unexpected graph state: $this")
+}
+
+@RequiresApi(21)
+internal fun CameraInternal.State.toCameraState(): CameraState.Type = when (this) {
+ CameraInternal.State.CLOSED -> CameraState.Type.CLOSED
+ CameraInternal.State.OPENING -> CameraState.Type.OPENING
+ CameraInternal.State.OPEN -> CameraState.Type.OPEN
+ CameraInternal.State.CLOSING -> CameraState.Type.CLOSING
+ CameraInternal.State.PENDING_OPEN -> CameraState.Type.PENDING_OPEN
+ else -> throw IllegalArgumentException("Unexpected CameraInternal state: $this")
+}
+
+internal fun MutableLiveData<CameraState>.setOrPostValue(cameraState: CameraState) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ this.value = cameraState
+ } else {
+ this.postValue(cameraState)
+ }
}
\ No newline at end of file
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
index bfef920..f529114 100644
--- 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
@@ -26,6 +26,7 @@
import androidx.camera.camera2.pipe.StreamFormat
import androidx.camera.camera2.pipe.StreamId
import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter.Companion.toCamera2ImplConfig
import androidx.camera.camera2.pipe.integration.impl.CameraCallbackMap
@@ -46,11 +47,13 @@
annotation class UseCaseCameraScope
/** Dependency bindings for building a [UseCaseCamera] */
-@Module(includes = [
- CapturePipelineImpl.Bindings::class,
- UseCaseCameraImpl.Bindings::class,
- UseCaseCameraRequestControlImpl.Bindings::class,
-])
+@Module(
+ includes = [
+ CapturePipelineImpl.Bindings::class,
+ UseCaseCameraImpl.Bindings::class,
+ UseCaseCameraRequestControlImpl.Bindings::class,
+ ]
+)
abstract class UseCaseCameraModule {
// Used for dagger provider methods that are static.
companion object
@@ -59,7 +62,8 @@
/** Dagger module for binding the [UseCase]'s to the [UseCaseCamera]. */
@Module
class UseCaseCameraConfig(
- private val useCases: List<UseCase>
+ private val useCases: List<UseCase>,
+ private val cameraStateAdapter: CameraStateAdapter,
) {
@UseCaseCameraScope
@Provides
@@ -105,11 +109,13 @@
}
// Build up a config (using TEMPLATE_PREVIEW by default)
- val graph = cameraPipe.create(CameraGraph.Config(
- camera = cameraConfig.cameraId,
- streams = streamConfigMap.keys.toList(),
- defaultListeners = listOf(callbackMap, requestListener),
- ))
+ val graph = cameraPipe.create(
+ CameraGraph.Config(
+ camera = cameraConfig.cameraId,
+ streams = streamConfigMap.keys.toList(),
+ defaultListeners = listOf(callbackMap, requestListener),
+ )
+ )
val surfaceToStreamMap = mutableMapOf<DeferrableSurface, StreamId>()
streamConfigMap.forEach { (streamConfig, deferrableSurface) ->
@@ -138,6 +144,7 @@
return UseCaseGraphConfig(
graph = graph,
surfaceToStreamMap = surfaceToStreamMap,
+ cameraStateAdapter = cameraStateAdapter,
)
}
}
@@ -145,6 +152,7 @@
data class UseCaseGraphConfig(
val graph: CameraGraph,
val surfaceToStreamMap: Map<DeferrableSurface, StreamId>,
+ val cameraStateAdapter: CameraStateAdapter,
)
/** Dagger subcomponent for a single [UseCaseCamera] instance. */
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 561805c..9ab3c3c 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
@@ -85,6 +85,7 @@
override val requestControl: UseCaseCameraRequestControl,
) : UseCaseCamera {
private val debugId = useCaseCameraIds.incrementAndGet()
+ private val graphStateJob: Job
override var runningUseCases = setOf<UseCase>()
set(value) {
@@ -105,9 +106,20 @@
init {
debug { "Configured $this for $useCases" }
+ useCaseGraphConfig.apply {
+ cameraStateAdapter.onGraphUpdated(graph)
+ }
+ graphStateJob = threads.scope.launch {
+ useCaseGraphConfig.apply {
+ graph.graphState.collect {
+ cameraStateAdapter.onGraphStateUpdated(graph, it)
+ }
+ }
+ }
}
override fun close(): Job {
+ graphStateJob.cancel()
return threads.scope.launch {
debug { "Closing $this" }
useCaseGraphConfig.graph.close()
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 537adf9..7021369 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
@@ -19,6 +19,7 @@
import androidx.annotation.GuardedBy
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
import androidx.camera.camera2.pipe.integration.config.CameraConfig
import androidx.camera.camera2.pipe.integration.config.CameraScope
import androidx.camera.camera2.pipe.integration.config.UseCaseCameraComponent
@@ -66,8 +67,9 @@
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") // Java version required for Dagger
private val controls: java.util.Set<UseCaseCameraControl>,
private val camera2CameraControl: Camera2CameraControl,
+ private val cameraStateAdapter: CameraStateAdapter,
cameraProperties: CameraProperties,
- displayInfoManager: DisplayInfoManager
+ displayInfoManager: DisplayInfoManager,
) {
private val lock = Any()
@@ -242,7 +244,7 @@
}
// Create and configure the new camera component.
- _activeComponent = builder.config(UseCaseCameraConfig(useCases)).build()
+ _activeComponent = builder.config(UseCaseCameraConfig(useCases, cameraStateAdapter)).build()
for (control in allControls) {
control.useCaseCamera = camera
}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraStateAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraStateAdapterTest.kt
new file mode 100644
index 0000000..7056538
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraStateAdapterTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2022 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.os.Build
+import androidx.camera.camera2.pipe.GraphState.GraphStateStarted
+import androidx.camera.camera2.pipe.GraphState.GraphStateStarting
+import androidx.camera.camera2.pipe.GraphState.GraphStateStopped
+import androidx.camera.camera2.pipe.GraphState.GraphStateStopping
+import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraph
+import androidx.camera.core.CameraState
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+@RunWith(RobolectricCameraPipeTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+internal class CameraStateAdapterTest {
+ private val cameraStateAdapter = CameraStateAdapter()
+ private val cameraGraph1 = FakeCameraGraph()
+ private val cameraGraph2 = FakeCameraGraph()
+
+ @Test
+ fun testNormalStateTransitions() {
+ cameraStateAdapter.onGraphUpdated(cameraGraph1)
+ assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.CLOSED)
+
+ cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStarting)
+ assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPENING)
+
+ cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStarted)
+ assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPEN)
+
+ cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStopped)
+ assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.CLOSED)
+ }
+
+ @Test
+ fun testStaleStateTransitions() {
+ cameraStateAdapter.onGraphUpdated(cameraGraph1)
+ assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.CLOSED)
+
+ cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStarting)
+ cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStarted)
+ assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPEN)
+
+ // Simulate that a new camera graph is created.
+ cameraStateAdapter.onGraphUpdated(cameraGraph2)
+ assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.CLOSED)
+ cameraStateAdapter.onGraphStateUpdated(cameraGraph2, GraphStateStarting)
+ assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPENING)
+
+ // This came from cameraGraph1 and thereby making the transition stale.
+ cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStopped)
+ assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPENING)
+
+ cameraStateAdapter.onGraphStateUpdated(cameraGraph2, GraphStateStarted)
+ assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPEN)
+ }
+
+ @Test
+ fun testImpermissibleStateTransitions() {
+ cameraStateAdapter.onGraphUpdated(cameraGraph1)
+ assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.CLOSED)
+
+ // Impermissible state transition from GraphStateStopped to GraphStateStopping
+ cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStopping)
+ assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.CLOSED)
+
+ cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStarting)
+ cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStarted)
+ assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPEN)
+
+ // Impermissible state transition from GraphStateStarted to GraphStateStarting
+ cameraStateAdapter.onGraphStateUpdated(cameraGraph1, GraphStateStarting)
+ assertThat(cameraStateAdapter.cameraState.value?.type).isEqualTo(CameraState.Type.OPEN)
+ }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapterTest.kt
index 252f425..34c2fa3 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CaptureConfigAdapterTest.kt
@@ -33,6 +33,7 @@
import androidx.camera.core.impl.TagBundle
import androidx.testutils.assertThrows
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executors
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -43,7 +44,6 @@
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
-import java.util.concurrent.Executors
@RunWith(RobolectricCameraPipeTestRunner::class)
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
@@ -66,6 +66,7 @@
useCaseGraphConfig = UseCaseGraphConfig(
graph = FakeCameraGraph(),
surfaceToStreamMap = mapOf(surface to StreamId(0)),
+ cameraStateAdapter = CameraStateAdapter(),
),
cameraProperties = fakeCameraProperties,
threads = fakeUseCaseThreads,
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
index 2b64455..f21cfe8 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
@@ -31,6 +31,7 @@
import androidx.camera.camera2.pipe.Request
import androidx.camera.camera2.pipe.RequestTemplate
import androidx.camera.camera2.pipe.Result3A
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
import androidx.camera.camera2.pipe.integration.adapter.asListenableFuture
import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
@@ -47,6 +48,11 @@
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.impl.utils.futures.Futures
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executors
+import java.util.concurrent.ScheduledFuture
+import java.util.concurrent.Semaphore
+import java.util.concurrent.TimeUnit
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
@@ -63,11 +69,6 @@
import org.mockito.Mockito.mock
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
-import java.util.concurrent.ExecutionException
-import java.util.concurrent.Executors
-import java.util.concurrent.ScheduledFuture
-import java.util.concurrent.Semaphore
-import java.util.concurrent.TimeUnit
@RunWith(RobolectricCameraPipeTestRunner::class)
@DoNotInstrument
@@ -199,6 +200,7 @@
useCaseGraphConfig = UseCaseGraphConfig(
graph = FakeCameraGraph(fakeCameraGraphSession = fakeCameraGraphSession),
surfaceToStreamMap = emptyMap(),
+ cameraStateAdapter = CameraStateAdapter(),
),
)
}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt
index f8902eb..0d3dc40 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControlTest.kt
@@ -24,6 +24,7 @@
import androidx.camera.camera2.pipe.Request
import androidx.camera.camera2.pipe.RequestMetadata
import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter
import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
@@ -39,6 +40,8 @@
import androidx.camera.core.impl.SessionConfig
import androidx.camera.core.impl.TagBundle
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -48,8 +51,6 @@
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
@RunWith(RobolectricCameraPipeTestRunner::class)
@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
@@ -72,6 +73,7 @@
private val fakeUseCaseGraphConfig = UseCaseGraphConfig(
graph = fakeCameraGraph,
surfaceToStreamMap = surfaceToStreamMap,
+ cameraStateAdapter = CameraStateAdapter(),
)
private val fakeConfigAdapter = CaptureConfigAdapter(
useCaseGraphConfig = fakeUseCaseGraphConfig,
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt
index 6126329..00097a2 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt
@@ -20,6 +20,7 @@
import android.os.Build
import androidx.camera.camera2.pipe.CameraPipe
import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter
import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
@@ -68,6 +69,7 @@
private val fakeUseCaseGraphConfig = UseCaseGraphConfig(
graph = fakeCameraGraph,
surfaceToStreamMap = surfaceToStreamMap,
+ cameraStateAdapter = CameraStateAdapter(),
)
private val fakeConfigAdapter = CaptureConfigAdapter(
useCaseGraphConfig = fakeUseCaseGraphConfig,
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
index 13ef7aa..a98af2c 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
@@ -18,6 +18,7 @@
import android.os.Build
import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
import androidx.camera.camera2.pipe.integration.config.CameraConfig
import androidx.camera.camera2.pipe.integration.interop.Camera2CameraControl
@@ -185,6 +186,7 @@
useCaseThreads,
ComboRequestListener()
),
- displayInfoManager = DisplayInfoManager(ApplicationProvider.getApplicationContext())
+ cameraStateAdapter = CameraStateAdapter(),
+ displayInfoManager = DisplayInfoManager(ApplicationProvider.getApplicationContext()),
)
}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt
index b99fa69..0aafb22 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/interop/Camera2CameraInfoTest.kt
@@ -19,6 +19,7 @@
import android.hardware.camera2.CameraCharacteristics
import android.os.Build
import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.integration.adapter.CameraControlStateAdapter
import androidx.camera.camera2.pipe.integration.adapter.CameraInfoAdapter
import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
@@ -198,7 +199,8 @@
return CameraInfoAdapter(
cameraProperties,
CameraConfig(cameraId),
- CameraStateAdapter(
+ CameraStateAdapter(),
+ CameraControlStateAdapter(
ZoomControl(FakeZoomCompat()),
EvCompControl(FakeEvCompCompat()),
TorchControl(cameraProperties, useCaseThreads),
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 c4c65e5..3b3ad4c 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
@@ -22,6 +22,7 @@
import androidx.camera.camera2.pipe.RequestTemplate
import androidx.camera.camera2.pipe.Result3A
import androidx.camera.camera2.pipe.StreamId
+import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
import androidx.camera.camera2.pipe.integration.config.UseCaseCameraComponent
import androidx.camera.camera2.pipe.integration.config.UseCaseCameraConfig
import androidx.camera.camera2.pipe.integration.impl.UseCaseCamera
@@ -35,7 +36,7 @@
import kotlinx.coroutines.Job
class FakeUseCaseCameraComponentBuilder : UseCaseCameraComponent.Builder {
- private var config: UseCaseCameraConfig = UseCaseCameraConfig(emptyList())
+ private var config: UseCaseCameraConfig = UseCaseCameraConfig(emptyList(), CameraStateAdapter())
override fun config(config: UseCaseCameraConfig): UseCaseCameraComponent.Builder {
this.config = config