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.adapter 18 19 import android.annotation.SuppressLint 20 import android.graphics.Rect 21 import android.hardware.camera2.CameraCharacteristics 22 import android.os.Build 23 import androidx.camera.camera2.pipe.CameraMetadata.Companion.supportsLowLightBoost 24 import androidx.camera.camera2.pipe.CameraPipe 25 import androidx.camera.camera2.pipe.core.Log.debug 26 import androidx.camera.camera2.pipe.core.Log.warn 27 import androidx.camera.camera2.pipe.integration.config.CameraScope 28 import androidx.camera.camera2.pipe.integration.impl.CameraProperties 29 import androidx.camera.camera2.pipe.integration.impl.EvCompControl 30 import androidx.camera.camera2.pipe.integration.impl.FlashControl 31 import androidx.camera.camera2.pipe.integration.impl.FocusMeteringControl 32 import androidx.camera.camera2.pipe.integration.impl.LowLightBoostControl 33 import androidx.camera.camera2.pipe.integration.impl.StillCaptureRequestControl 34 import androidx.camera.camera2.pipe.integration.impl.TorchControl 35 import androidx.camera.camera2.pipe.integration.impl.UseCaseCamera 36 import androidx.camera.camera2.pipe.integration.impl.UseCaseManager 37 import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads 38 import androidx.camera.camera2.pipe.integration.impl.VideoUsageControl 39 import androidx.camera.camera2.pipe.integration.impl.ZoomControl 40 import androidx.camera.camera2.pipe.integration.interop.Camera2CameraControl 41 import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions 42 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop 43 import androidx.camera.core.CameraControl.OperationCanceledException 44 import androidx.camera.core.FocusMeteringAction 45 import androidx.camera.core.FocusMeteringResult 46 import androidx.camera.core.ImageCapture 47 import androidx.camera.core.ImageCapture.FLASH_MODE_AUTO 48 import androidx.camera.core.ImageCapture.FLASH_MODE_ON 49 import androidx.camera.core.LowLightBoostState 50 import androidx.camera.core.TorchState 51 import androidx.camera.core.imagecapture.CameraCapturePipeline 52 import androidx.camera.core.impl.CameraControlInternal 53 import androidx.camera.core.impl.CaptureConfig 54 import androidx.camera.core.impl.Config 55 import androidx.camera.core.impl.SessionConfig 56 import androidx.camera.core.impl.utils.executor.CameraXExecutors 57 import androidx.camera.core.impl.utils.futures.Futures 58 import com.google.common.util.concurrent.ListenableFuture 59 import javax.inject.Inject 60 import kotlinx.coroutines.CompletableDeferred 61 import kotlinx.coroutines.ExperimentalCoroutinesApi 62 63 /** 64 * Adapt the [CameraControlInternal] interface to [CameraPipe]. 65 * 66 * This controller class maintains state as use-cases are attached / detached from the camera as 67 * well as providing access to other utility methods. The primary purpose of this class it to 68 * forward these interactions to the currently configured [UseCaseCamera]. 69 */ 70 @SuppressLint("UnsafeOptInUsageError") 71 @CameraScope 72 @OptIn(ExperimentalCoroutinesApi::class, ExperimentalCamera2Interop::class) 73 public class CameraControlAdapter 74 @Inject 75 constructor( 76 private val cameraProperties: CameraProperties, 77 private val evCompControl: EvCompControl, 78 private val flashControl: FlashControl, 79 private val focusMeteringControl: FocusMeteringControl, 80 private val stillCaptureRequestControl: StillCaptureRequestControl, 81 private val torchControl: TorchControl, 82 private val lowLightBoostControl: LowLightBoostControl, 83 private val zoomControl: ZoomControl, 84 private val zslControl: ZslControl, 85 public val camera2cameraControl: Camera2CameraControl, 86 private val useCaseManager: UseCaseManager, 87 private val threads: UseCaseThreads, 88 private val videoUsageControl: VideoUsageControl, 89 ) : CameraControlInternal { getSensorRectnull90 override fun getSensorRect(): Rect { 91 val sensorRect = 92 cameraProperties.metadata[CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE] 93 if ("robolectric" == Build.FINGERPRINT && sensorRect == null) { 94 return Rect(0, 0, 4000, 3000) 95 } 96 return sensorRect!! 97 } 98 addInteropConfignull99 override fun addInteropConfig(config: Config) { 100 camera2cameraControl.addCaptureRequestOptions( 101 CaptureRequestOptions.Builder.from(config).build() 102 ) 103 } 104 clearInteropConfignull105 override fun clearInteropConfig() { 106 camera2cameraControl.clearCaptureRequestOptions() 107 } 108 getInteropConfignull109 override fun getInteropConfig(): Config { 110 return camera2cameraControl.getCaptureRequestOptions() 111 } 112 enableTorchnull113 override fun enableTorch(torch: Boolean): ListenableFuture<Void> { 114 if ( 115 cameraProperties.metadata.supportsLowLightBoost && 116 lowLightBoostControl.lowLightBoostStateLiveData.value != LowLightBoostState.OFF 117 ) { 118 debug { "Unable to enable/disable torch when low-light boost is on." } 119 return Futures.immediateFailedFuture<Void>( 120 IllegalStateException( 121 "Torch can not be enabled/disable when low-light boost is on!" 122 ) 123 ) 124 } 125 126 return Futures.nonCancellationPropagating( 127 torchControl.setTorchAsync(torch).asVoidListenableFuture() 128 ) 129 } 130 setTorchStrengthLevelnull131 override fun setTorchStrengthLevel(torchStrengthLevel: Int): ListenableFuture<Void> = 132 Futures.nonCancellationPropagating( 133 torchControl.setTorchStrengthLevelAsync(torchStrengthLevel).asVoidListenableFuture() 134 ) 135 136 override fun enableLowLightBoostAsync(lowLightBoost: Boolean): ListenableFuture<Void> { 137 if (!cameraProperties.metadata.supportsLowLightBoost) { 138 debug { "Unable to enable/disable low-light boost due to it is not supported." } 139 return Futures.immediateFailedFuture<Void>( 140 IllegalStateException("Low-light boost is not supported!") 141 ) 142 } 143 144 return Futures.nonCancellationPropagating( 145 Futures.transformAsync( 146 if (torchControl.torchStateLiveData.value == TorchState.ON) { 147 torchControl.setTorchAsync(false).asVoidListenableFuture() 148 } else { 149 CompletableDeferred(Unit).apply { complete(Unit) }.asVoidListenableFuture() 150 }, 151 { 152 lowLightBoostControl 153 .setLowLightBoostAsync(lowLightBoost) 154 .asVoidListenableFuture() 155 }, 156 CameraXExecutors.directExecutor() 157 ) 158 ) 159 } 160 startFocusAndMeteringnull161 override fun startFocusAndMetering( 162 action: FocusMeteringAction 163 ): ListenableFuture<FocusMeteringResult> = 164 Futures.nonCancellationPropagating(focusMeteringControl.startFocusAndMetering(action)) 165 166 override fun cancelFocusAndMetering(): ListenableFuture<Void> { 167 return Futures.nonCancellationPropagating( 168 CompletableDeferred<Void?>() 169 .also { 170 // Convert to null once the task is done, ignore the results. 171 focusMeteringControl.cancelFocusAndMeteringAsync().propagateTo(it) { null } 172 } 173 .asListenableFuture() 174 ) 175 } 176 setZoomRationull177 override fun setZoomRatio(ratio: Float): ListenableFuture<Void> = 178 zoomControl.setZoomRatio(ratio) 179 180 override fun setLinearZoom(linearZoom: Float): ListenableFuture<Void> = 181 zoomControl.setLinearZoom(linearZoom) 182 183 override fun getFlashMode(): Int { 184 return flashControl.flashMode 185 } 186 setFlashModenull187 override fun setFlashMode(@ImageCapture.FlashMode flashMode: Int) { 188 flashControl.setFlashAsync(flashMode) 189 zslControl.setZslDisabledByFlashMode( 190 flashMode == FLASH_MODE_ON || flashMode == FLASH_MODE_AUTO 191 ) 192 } 193 setScreenFlashnull194 override fun setScreenFlash(screenFlash: ImageCapture.ScreenFlash?) { 195 flashControl.setScreenFlash(screenFlash) 196 } 197 setExposureCompensationIndexnull198 override fun setExposureCompensationIndex(exposure: Int): ListenableFuture<Int> = 199 Futures.nonCancellationPropagating(evCompControl.updateAsync(exposure).asListenableFuture()) 200 201 override fun setZslDisabledByUserCaseConfig(disabled: Boolean) { 202 zslControl.setZslDisabledByUserCaseConfig(disabled) 203 } 204 isZslDisabledByByUserCaseConfignull205 override fun isZslDisabledByByUserCaseConfig(): Boolean { 206 return zslControl.isZslDisabledByUserCaseConfig() 207 } 208 addZslConfignull209 override fun addZslConfig(sessionConfigBuilder: SessionConfig.Builder) { 210 zslControl.addZslConfig(sessionConfigBuilder) 211 } 212 setLowLightBoostDisabledByUseCaseSessionConfignull213 override fun setLowLightBoostDisabledByUseCaseSessionConfig(disabled: Boolean) { 214 lowLightBoostControl.setLowLightBoostDisabledByUseCaseSessionConfig(disabled) 215 } 216 clearZslConfignull217 override fun clearZslConfig() { 218 zslControl.clearZslConfig() 219 } 220 submitStillCaptureRequestsnull221 override fun submitStillCaptureRequests( 222 captureConfigs: List<CaptureConfig>, 223 @ImageCapture.CaptureMode captureMode: Int, 224 @ImageCapture.FlashType flashType: Int, 225 ): ListenableFuture<List<Void?>> = 226 stillCaptureRequestControl.issueCaptureRequests(captureConfigs, captureMode, flashType) 227 228 override fun getCameraCapturePipelineAsync( 229 @ImageCapture.CaptureMode captureMode: Int, 230 @ImageCapture.FlashType flashType: Int 231 ): ListenableFuture<CameraCapturePipeline> { 232 val camera = 233 useCaseManager.camera 234 ?: return Futures.immediateFailedFuture( 235 OperationCanceledException("Camera is not active.") 236 ) 237 return threads.sequentialScope.future { 238 camera.getCameraCapturePipeline( 239 captureMode, 240 flashControl.awaitFlashModeUpdate(), 241 flashType 242 ) 243 } 244 } 245 getSessionConfignull246 override fun getSessionConfig(): SessionConfig { 247 warn { "TODO: getSessionConfig is not yet supported" } 248 return SessionConfig.defaultEmptySessionConfig() 249 } 250 incrementVideoUsagenull251 override fun incrementVideoUsage() { 252 videoUsageControl.incrementUsage() 253 } 254 decrementVideoUsagenull255 override fun decrementVideoUsage() { 256 videoUsageControl.decrementUsage() 257 } 258 isInVideoUsagenull259 override fun isInVideoUsage(): Boolean = videoUsageControl.isInVideoUsage() 260 } 261