1 /*
<lambda>null2  * Copyright 2021 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
18 
19 import androidx.annotation.GuardedBy
20 import androidx.camera.camera2.pipe.FrameInfo
21 import androidx.camera.camera2.pipe.FrameNumber
22 import androidx.camera.camera2.pipe.Request
23 import androidx.camera.camera2.pipe.RequestMetadata
24 import androidx.camera.camera2.pipe.integration.adapter.propagateTo
25 import androidx.camera.camera2.pipe.integration.config.CameraScope
26 import androidx.camera.camera2.pipe.integration.impl.Camera2ImplConfig
27 import androidx.camera.camera2.pipe.integration.impl.UseCaseCameraRequestControl
28 import androidx.camera.camera2.pipe.integration.impl.containsTag
29 import androidx.camera.camera2.pipe.integration.interop.CaptureRequestOptions
30 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
31 import androidx.camera.core.CameraControl
32 import androidx.camera.core.impl.Config
33 import androidx.camera.core.impl.annotation.ExecutedBy
34 import dagger.Binds
35 import dagger.Module
36 import javax.inject.Inject
37 import kotlinx.coroutines.CompletableDeferred
38 import kotlinx.coroutines.Deferred
39 
40 private const val TAG_KEY = "Camera2CameraControl.tag"
41 
42 @JvmDefaultWithCompatibility
43 @ExperimentalCamera2Interop
44 public interface Camera2CameraControlCompat : Request.Listener {
45     public fun addRequestOption(bundle: CaptureRequestOptions)
46 
47     public fun getRequestOption(): CaptureRequestOptions
48 
49     public fun clearRequestOption()
50 
51     public fun cancelCurrentTask()
52 
53     public fun applyAsync(
54         requestControl: UseCaseCameraRequestControl?,
55         cancelPreviousTask: Boolean = true
56     ): Deferred<Void?>
57 
58     @Module
59     public abstract class Bindings {
60         @Binds
61         public abstract fun bindCamera2CameraControlCompImpl(
62             impl: Camera2CameraControlCompatImpl
63         ): Camera2CameraControlCompat
64     }
65 }
66 
67 @CameraScope
68 @ExperimentalCamera2Interop
69 public class Camera2CameraControlCompatImpl @Inject constructor() : Camera2CameraControlCompat {
70 
71     private val lock = Any()
72     private val updateSignalLock = Any()
73 
74     @GuardedBy("lock") private var configBuilder = Camera2ImplConfig.Builder()
75     @GuardedBy("updateSignalLock") private var updateSignal: CompletableDeferred<Void?>? = null
76     @GuardedBy("updateSignalLock") private var pendingSignal: CompletableDeferred<Void?>? = null
77 
addRequestOptionnull78     override fun addRequestOption(bundle: CaptureRequestOptions) {
79         synchronized(lock) {
80             for (option in bundle.listOptions()) {
81                 @Suppress("UNCHECKED_CAST") val objectOpt = option as Config.Option<Any>
82                 configBuilder.mutableConfig.insertOption(
83                     objectOpt,
84                     Config.OptionPriority.ALWAYS_OVERRIDE,
85                     bundle.retrieveOption(objectOpt)
86                 )
87             }
88         }
89     }
90 
getRequestOptionnull91     override fun getRequestOption(): CaptureRequestOptions =
92         synchronized(lock) { CaptureRequestOptions.Builder.from(configBuilder.build()).build() }
93 
clearRequestOptionnull94     override fun clearRequestOption() {
95         synchronized(lock) { configBuilder = Camera2ImplConfig.Builder() }
96     }
97 
cancelCurrentTasknull98     override fun cancelCurrentTask(): Unit =
99         synchronized(updateSignalLock) {
100             updateSignal
101                 ?.also { updateSignal = null }
102                 ?.cancelSignal("The camera control has became inactive.")
103             pendingSignal
104                 ?.also { pendingSignal = null }
105                 ?.cancelSignal("The camera control has became inactive.")
106         }
107 
applyAsyncnull108     override fun applyAsync(
109         requestControl: UseCaseCameraRequestControl?,
110         cancelPreviousTask: Boolean
111     ): Deferred<Void?> {
112         val signal: CompletableDeferred<Void?> = CompletableDeferred()
113         val config = synchronized(lock) { configBuilder.build() }
114         synchronized(updateSignalLock) {
115             if (requestControl != null) {
116                 if (cancelPreviousTask) {
117                     // Cancel the previous request signal if exist.
118                     updateSignal?.cancelSignal()
119                 } else {
120                     // propagate the result to the previous updateSignal
121                     updateSignal?.let { previousUpdateSignal ->
122                         signal.propagateTo(previousUpdateSignal)
123                     }
124                 }
125 
126                 updateSignal = signal
127                 requestControl.setConfigAsync(
128                     type = UseCaseCameraRequestControl.Type.CAMERA2_CAMERA_CONTROL,
129                     config = config,
130                     tags = mapOf(TAG_KEY to signal.hashCode())
131                 )
132             } else {
133                 // If there is no camera for the parameter update, the signal would be treated as a
134                 // pending signal, and the pending signal would be completed after the camera
135                 // applied the parameter.
136 
137                 // Cancel the previous request signal if it exists. Only keep the latest signal.
138                 pendingSignal?.cancelSignal()
139                 pendingSignal = signal
140             }
141         }
142 
143         return signal
144     }
145 
cancelSignalnull146     private fun CompletableDeferred<Void?>.cancelSignal(
147         msg: String = "Camera2CameraControl was updated with new options."
148     ) = this.apply { completeExceptionally(CameraControl.OperationCanceledException(msg)) }
149 
150     @ExecutedBy("UseCaseThreads")
onCompletenull151     override fun onComplete(
152         requestMetadata: RequestMetadata,
153         frameNumber: FrameNumber,
154         result: FrameInfo
155     ): Unit =
156         synchronized(updateSignalLock) {
157             updateSignal?.apply {
158                 if (requestMetadata.containsTag(TAG_KEY, hashCode())) {
159                     // Going to complete the [updateSignal] if the result contains the [TAG_KEY]
160                     complete(null)
161                     updateSignal = null
162 
163                     // Also complete the [pendingSignal] if it exists.
164                     pendingSignal?.also {
165                         it.complete(null)
166                         pendingSignal = null
167                     }
168                 }
169             }
170         }
171 }
172