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