1 /*
2  * Copyright 2023 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.compat.workaround
18 
19 import android.hardware.camera2.CameraDevice
20 import androidx.camera.camera2.pipe.CameraMetadata.Companion.isHardwareLevelLegacy
21 import androidx.camera.camera2.pipe.RequestTemplate
22 import androidx.camera.camera2.pipe.core.Log
23 import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter.Companion.getStillCaptureTemplate
24 import androidx.camera.camera2.pipe.integration.compat.quirk.DeviceQuirks
25 import androidx.camera.camera2.pipe.integration.compat.quirk.TorchIsClosedAfterImageCapturingQuirk
26 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
27 import androidx.camera.camera2.pipe.integration.impl.CameraProperties
28 import androidx.camera.camera2.pipe.integration.impl.CapturePipeline
29 import androidx.camera.camera2.pipe.integration.impl.CapturePipelineImpl
30 import androidx.camera.camera2.pipe.integration.impl.TorchControl
31 import androidx.camera.camera2.pipe.integration.impl.TorchControl.TorchMode
32 import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
33 import androidx.camera.core.ImageCapture
34 import androidx.camera.core.TorchState
35 import androidx.camera.core.imagecapture.CameraCapturePipeline
36 import androidx.camera.core.impl.CaptureConfig
37 import androidx.camera.core.impl.Config
38 import javax.inject.Inject
39 import kotlinx.coroutines.Deferred
40 import kotlinx.coroutines.joinAll
41 import kotlinx.coroutines.launch
42 
43 /**
44  * This is a workaround for b/228272227 where the Torch is unexpectedly closed after a single
45  * capturing.
46  *
47  * If the Torch is enabled before performing a single capture, this workaround may turn the Torch
48  * OFF then ON after the capturing.
49  */
50 @UseCaseCameraScope
51 public class CapturePipelineTorchCorrection
52 @Inject
53 constructor(
54     cameraProperties: CameraProperties,
55     private val capturePipelineImpl: CapturePipelineImpl,
56     private val threads: UseCaseThreads,
57     private val torchControl: TorchControl,
58 ) : CapturePipeline {
59     private val isLegacyDevice = cameraProperties.metadata.isHardwareLevelLegacy
60 
submitStillCapturesnull61     override suspend fun submitStillCaptures(
62         configs: List<CaptureConfig>,
63         requestTemplate: RequestTemplate,
64         sessionConfigOptions: Config,
65         @ImageCapture.CaptureMode captureMode: Int,
66         @ImageCapture.FlashType flashType: Int,
67         @ImageCapture.FlashMode flashMode: Int
68     ): List<Deferred<Void?>> {
69         val needCorrectTorchState = isCorrectionRequired(configs, requestTemplate)
70 
71         // Forward the capture request to capturePipelineImpl
72         val deferredResults =
73             capturePipelineImpl.submitStillCaptures(
74                 configs,
75                 requestTemplate,
76                 sessionConfigOptions,
77                 captureMode,
78                 flashType,
79                 flashMode
80             )
81 
82         if (needCorrectTorchState) {
83             threads.sequentialScope.launch {
84                 deferredResults.joinAll()
85                 Log.debug { "Re-enable Torch to correct the Torch state" }
86                 torchControl.setTorchAsync(TorchMode.OFF).join()
87                 torchControl.setTorchAsync(TorchMode.USED_AS_FLASH).join()
88                 Log.debug { "Re-enable Torch to correct the Torch state, done" }
89             }
90         }
91 
92         return deferredResults
93     }
94 
getCameraCapturePipelinenull95     override suspend fun getCameraCapturePipeline(
96         captureMode: Int,
97         flashMode: Int,
98         flashType: Int
99     ): CameraCapturePipeline =
100         capturePipelineImpl.getCameraCapturePipeline(captureMode, flashMode, flashType)
101 
102     override var template: Int = CameraDevice.TEMPLATE_PREVIEW
103         set(value) {
104             capturePipelineImpl.template = value
105             field = value
106         }
107 
108     /**
109      * Return true means the Torch will be unexpectedly closed, and it requires turning on the Torch
110      * again after the capturing.
111      */
isCorrectionRequirednull112     private fun isCorrectionRequired(
113         captureConfigs: List<CaptureConfig>,
114         requestTemplate: RequestTemplate,
115     ): Boolean {
116         return captureConfigs.any {
117             it.getStillCaptureTemplate(
118                     requestTemplate,
119                     isLegacyDevice,
120                 )
121                 .value == CameraDevice.TEMPLATE_STILL_CAPTURE
122         } && isTorchOn()
123     }
124 
isTorchOnnull125     private fun isTorchOn() = torchControl.torchStateLiveData.value == TorchState.ON
126 
127     public companion object {
128         public val isEnabled: Boolean =
129             DeviceQuirks[TorchIsClosedAfterImageCapturingQuirk::class.java] != null
130     }
131 }
132