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