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