1 /*
<lambda>null2  * 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 androidx.annotation.GuardedBy
20 import androidx.camera.camera2.pipe.core.Log.debug
21 import androidx.camera.camera2.pipe.integration.adapter.asListenableFuture
22 import androidx.camera.camera2.pipe.integration.adapter.propagateCompletion
23 import androidx.camera.camera2.pipe.integration.config.CameraScope
24 import androidx.camera.core.ImageCapture
25 import androidx.camera.core.ImageCaptureException
26 import androidx.camera.core.impl.CaptureConfig
27 import androidx.camera.core.impl.utils.futures.Futures
28 import com.google.common.util.concurrent.ListenableFuture
29 import dagger.Binds
30 import dagger.Module
31 import dagger.multibindings.IntoSet
32 import java.util.LinkedList
33 import javax.inject.Inject
34 import kotlinx.coroutines.CompletableDeferred
35 import kotlinx.coroutines.Deferred
36 import kotlinx.coroutines.async
37 import kotlinx.coroutines.awaitAll
38 import kotlinx.coroutines.launch
39 import kotlinx.coroutines.sync.Mutex
40 import kotlinx.coroutines.sync.withLock
41 
42 @CameraScope
43 public class StillCaptureRequestControl
44 @Inject
45 constructor(
46     private val flashControl: FlashControl,
47     private val threads: UseCaseThreads,
48 ) : UseCaseCameraControl {
49     private val mutex = Mutex()
50 
51     private var _requestControl: UseCaseCameraRequestControl? = null
52     override var requestControl: UseCaseCameraRequestControl?
53         get() = _requestControl
54         set(value) {
55             _requestControl = value
56             trySubmitPendingRequests()
57         }
58 
59     public data class CaptureRequest(
60         val captureConfigs: List<CaptureConfig>,
61         @ImageCapture.CaptureMode val captureMode: Int,
62         @ImageCapture.FlashType val flashType: Int,
63         val result: CompletableDeferred<List<Void?>>,
64     )
65 
66     /**
67      * These requests failed to be completely processed with some UseCaseCamera that was open when
68      * corresponding request was issued. (e.g. UseCaseCamera was closed for recreation) Thus, these
69      * requests should be retried when a new UseCaseCamera is created.
70      */
71     @GuardedBy("mutex") private val pendingRequests = LinkedList<CaptureRequest>()
72 
73     override fun reset() {
74         threads.sequentialScope.launch {
75             mutex.withLock {
76                 while (pendingRequests.isNotEmpty()) {
77                     pendingRequests
78                         .poll()
79                         ?.result
80                         ?.completeExceptionally(
81                             ImageCaptureException(
82                                 ImageCapture.ERROR_CAMERA_CLOSED,
83                                 "Capture request is cancelled due to a reset",
84                                 null
85                             )
86                         )
87                 }
88             }
89         }
90     }
91 
92     public fun issueCaptureRequests(
93         captureConfigs: List<CaptureConfig>,
94         @ImageCapture.CaptureMode captureMode: Int,
95         @ImageCapture.FlashType flashType: Int,
96     ): ListenableFuture<List<Void?>> {
97         val signal = CompletableDeferred<List<Void?>>()
98 
99         threads.sequentialScope.launch {
100             val request = CaptureRequest(captureConfigs, captureMode, flashType, signal)
101             val requestControl = requestControl
102             if (requestControl != null && requestControl.awaitSurfaceSetup()) {
103                 submitRequest(request, requireNotNull(requestControl))
104                     .propagateResultOrEnqueueRequest(request, requireNotNull(requestControl))
105             } else {
106                 // UseCaseCamera may become null by the time the coroutine is started
107                 mutex.withLock { pendingRequests.add(request) }
108                 debug {
109                     "StillCaptureRequestControl: useCaseCamera is null, $request" +
110                         " will be retried with a future UseCaseCamera"
111                 }
112             }
113         }
114 
115         return Futures.nonCancellationPropagating(signal.asListenableFuture())
116     }
117 
118     private fun trySubmitPendingRequests() {
119         threads.sequentialScope.launch {
120             val requestControl = requestControl ?: return@launch
121             if (requestControl.awaitSurfaceSetup()) {
122                 mutex.withLock {
123                     while (pendingRequests.isNotEmpty()) {
124                         pendingRequests.poll()?.let { request ->
125                             requestControl.let { requestControl ->
126                                 submitRequest(request, requestControl)
127                                     .propagateResultOrEnqueueRequest(
128                                         submittedRequest = request,
129                                         currentRequestControl = requestControl
130                                     )
131                             }
132                         }
133                     }
134                 }
135             }
136         }
137     }
138 
139     private suspend fun submitRequest(
140         request: CaptureRequest,
141         requestControl: UseCaseCameraRequestControl
142     ): Deferred<List<Void?>> {
143         debug { "StillCaptureRequestControl: submitting $request at $requestControl" }
144         // Prior to submitStillCaptures, wait until the pending flash mode session change is
145         // completed. On some devices, AE preCapture triggered in submitStillCaptures may not
146         // work properly if the repeating request to change the flash mode is not completed.
147         val flashMode = flashControl.awaitFlashModeUpdate()
148         debug { "StillCaptureRequestControl: Issuing single capture" }
149         val deferredList =
150             requestControl.issueSingleCaptureAsync(
151                 request.captureConfigs,
152                 request.captureMode,
153                 request.flashType,
154                 flashMode,
155             )
156 
157         return threads.sequentialScope.async {
158             // requestControl.issueSingleCaptureAsync shouldn't be invoked from here directly,
159             // because sequentialScope.async is may not be executed immediately
160             debug { "StillCaptureRequestControl: Waiting for deferred list from $request" }
161             deferredList.awaitAll().also {
162                 debug { "StillCaptureRequestControl: Waiting for deferred list from $request done" }
163             }
164         }
165     }
166 
167     private fun Deferred<List<Void?>>.propagateResultOrEnqueueRequest(
168         submittedRequest: CaptureRequest,
169         currentRequestControl: UseCaseCameraRequestControl
170     ) {
171         invokeOnCompletion { cause: Throwable? ->
172             if (
173                 cause is ImageCaptureException &&
174                     cause.imageCaptureError == ImageCapture.ERROR_CAMERA_CLOSED
175             ) {
176                 threads.sequentialScope.launch {
177                     var isPending = true
178 
179                     requestControl?.let { latestRequestControl ->
180                         if (currentRequestControl != latestRequestControl) {
181                             // camera has already been changed, can retry immediately
182                             submitRequest(submittedRequest, latestRequestControl)
183                                 .propagateResultOrEnqueueRequest(
184                                     submittedRequest = submittedRequest,
185                                     currentRequestControl = latestRequestControl
186                                 )
187                             isPending = false
188                         }
189                     }
190 
191                     // no new camera to retry at, adding to pending list for trying later
192                     if (isPending) {
193                         mutex.withLock { pendingRequests.add(submittedRequest) }
194                         debug {
195                             "StillCaptureRequestControl: failed to submit $submittedRequest" +
196                                 ", will be retried with a future UseCaseCamera"
197                         }
198                     }
199                 }
200             } else {
201                 propagateCompletion(submittedRequest.result, cause)
202             }
203         }
204     }
205 
206     @Module
207     public abstract class Bindings {
208         @Binds
209         @IntoSet
210         public abstract fun provideControls(
211             control: StillCaptureRequestControl
212         ): UseCaseCameraControl
213     }
214 }
215