1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.camera.camera2.pipe.integration.impl
18 
19 import android.hardware.camera2.CameraDevice
20 import androidx.camera.camera2.pipe.CameraGraph
21 import androidx.camera.camera2.pipe.GraphState.GraphStateError
22 import androidx.camera.camera2.pipe.GraphState.GraphStateStarted
23 import androidx.camera.camera2.pipe.GraphState.GraphStateStopped
24 import androidx.camera.camera2.pipe.core.Log.debug
25 import androidx.camera.camera2.pipe.integration.adapter.RequestProcessorAdapter
26 import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
27 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
28 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
29 import androidx.camera.core.ImageCapture
30 import androidx.camera.core.UseCase
31 import androidx.camera.core.imagecapture.CameraCapturePipeline
32 import androidx.camera.core.impl.Config
33 import androidx.camera.core.impl.SessionProcessorSurface
34 import dagger.Binds
35 import dagger.Module
36 import javax.inject.Inject
37 import kotlinx.atomicfu.atomic
38 import kotlinx.coroutines.CompletableDeferred
39 import kotlinx.coroutines.CoroutineStart
40 import kotlinx.coroutines.Job
41 import kotlinx.coroutines.flow.first
42 import kotlinx.coroutines.launch
43 
44 internal val useCaseCameraIds = atomic(0)
45 internal val defaultOptionPriority = Config.OptionPriority.OPTIONAL
46 internal const val defaultTemplate = CameraDevice.TEMPLATE_PREVIEW
47 
48 @JvmDefaultWithCompatibility
49 public interface UseCaseCamera {
50     // RequestControl of the UseCaseCamera
51     public val requestControl: UseCaseCameraRequestControl
52 
getCameraCapturePipelinenull53     public suspend fun getCameraCapturePipeline(
54         @ImageCapture.CaptureMode captureMode: Int,
55         @ImageCapture.FlashMode flashMode: Int,
56         @ImageCapture.FlashType flashType: Int,
57     ): CameraCapturePipeline
58 
59     public fun setActiveResumeMode(enabled: Boolean) {}
60 
61     // Lifecycle
closenull62     public fun close(): Job
63 }
64 
65 /** API for interacting with a [CameraGraph] that has been configured with a set of [UseCase]'s */
66 @UseCaseCameraScope
67 @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
68 // Java version required for Dagger
69 public class UseCaseCameraImpl
70 @Inject
71 constructor(
72     private val useCaseGraphConfig: UseCaseGraphConfig,
73     private val useCases: java.util.ArrayList<UseCase>,
74     private val useCaseSurfaceManager: UseCaseSurfaceManager,
75     private val threads: UseCaseThreads,
76     private val sessionProcessorManager: SessionProcessorManager?,
77     private val sessionConfigAdapter: SessionConfigAdapter,
78     override val requestControl: UseCaseCameraRequestControl,
79     private val capturePipeline: CapturePipeline
80 ) : UseCaseCamera {
81     private val debugId = useCaseCameraIds.incrementAndGet()
82     private val closed = atomic(false)
83 
84     init {
85         debug { "Configured $this for $useCases" }
86         useCaseGraphConfig.apply { cameraStateAdapter.onGraphUpdated(graph) }
87         threads.scope.launch {
88             useCaseGraphConfig.apply {
89                 graph.graphState.collect {
90                     cameraStateAdapter.onGraphStateUpdated(graph, it)
91 
92                     // Even if the UseCaseCamera is closed, we should still update the GraphState
93                     // before cancelling the job, because it could be the last UseCaseCamera created
94                     // (i.e., no new UseCaseCamera to update CameraStateAdapter that this one as
95                     // stopped/closed).
96                     if (closed.value && it is GraphStateStopped || it is GraphStateError) {
97                         this@launch.coroutineContext[Job]?.cancel()
98                     }
99 
100                     // TODO: b/323614735: Technically our RequestProcessor implementation could be
101                     //   given to the SessionProcessor through onCaptureSessionStart after the
102                     //   new set of configurations (CameraGraph) is created. However, this seems to
103                     //   be causing occasional SIGBUS on the Android platform level. Delaying this
104                     //   seems to be mitigating the issue, but does result in overhead in startup
105                     //   latencies. Move this back to UseCaseManager once we understand more about
106                     //   the situation.
107                     if (sessionProcessorManager != null && it is GraphStateStarted) {
108                         val sessionProcessorSurfaces =
109                             sessionConfigAdapter.deferrableSurfaces.map {
110                                 it as SessionProcessorSurface
111                             }
112                         val requestProcessorAdapter =
113                             RequestProcessorAdapter(
114                                 useCaseGraphConfig,
115                                 sessionProcessorSurfaces,
116                                 threads
117                             )
118                         sessionProcessorManager.onCaptureSessionStart(requestProcessorAdapter)
119                     }
120                 }
121             }
122         }
123     }
124 
125     override fun close(): Job {
126         return if (closed.compareAndSet(expect = false, update = true)) {
127             threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
128                 debug { "Closing $this" }
129                 requestControl.close()
130                 sessionProcessorManager?.prepareClose()
131                 useCaseGraphConfig.graph.close()
132                 if (sessionProcessorManager != null) {
133                     useCaseGraphConfig.graph.graphState.first {
134                         it is GraphStateStopped || it is GraphStateError
135                     }
136                     sessionProcessorManager.close()
137                 }
138                 useCaseSurfaceManager.stopAsync().await()
139             }
140         } else {
141             CompletableDeferred(Unit)
142         }
143     }
144 
145     override fun setActiveResumeMode(enabled: Boolean) {
146         useCaseGraphConfig.graph.isForeground = enabled
147     }
148 
149     override fun toString(): String = "UseCaseCamera-$debugId"
150 
151     override suspend fun getCameraCapturePipeline(
152         @ImageCapture.CaptureMode captureMode: Int,
153         @ImageCapture.FlashMode flashMode: Int,
154         @ImageCapture.FlashType flashType: Int,
155     ): CameraCapturePipeline =
156         capturePipeline.getCameraCapturePipeline(captureMode, flashMode, flashType)
157 
158     @Module
159     public abstract class Bindings {
160         @UseCaseCameraScope
161         @Binds
162         public abstract fun provideUseCaseCamera(useCaseCamera: UseCaseCameraImpl): UseCaseCamera
163     }
164 }
165