1 /*
2  * 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.testing
18 
19 import android.view.Surface
20 import androidx.annotation.GuardedBy
21 import androidx.camera.camera2.pipe.CameraId
22 import androidx.camera.camera2.pipe.CaptureSequence.CaptureSequenceListener
23 import androidx.camera.camera2.pipe.CaptureSequenceProcessor
24 import androidx.camera.camera2.pipe.Request
25 import androidx.camera.camera2.pipe.RequestTemplate
26 import androidx.camera.camera2.pipe.StreamId
27 import kotlinx.atomicfu.atomic
28 
29 /**
30  * Fake implementation of a [CaptureSequenceProcessor] that records events and simulates some low
31  * level behavior.
32  *
33  * This allows kotlin tests to check sequences of interactions that dispatch in the background
34  * without blocking between events.
35  */
36 public class FakeCaptureSequenceProcessor(
37     private val cameraId: CameraId = FakeCameraIds.default,
38     private val defaultTemplate: RequestTemplate = RequestTemplate(1)
39 ) : CaptureSequenceProcessor<Request, FakeCaptureSequence> {
40     private val debugId = debugIds.incrementAndGet()
41     private val lock = Any()
42     private val sequenceIds = atomic(0)
43 
44     @GuardedBy("lock") private val captureQueue = mutableListOf<FakeCaptureSequence>()
45 
46     @GuardedBy("lock") private var repeatingCapture: FakeCaptureSequence? = null
47 
48     @GuardedBy("lock") private var shutdown = false
49 
50     @GuardedBy("lock") private val _events = mutableListOf<Event>()
51 
52     @GuardedBy("lock") private var nextEventIndex = 0
53     public val events: List<Event>
<lambda>null54         get() = synchronized(lock) { _events }
55 
56     /** Get the next event from queue with an option to specify a timeout for tests. */
nextEventnull57     public fun nextEvent(): Event {
58         synchronized(lock) {
59             val eventIdx = nextEventIndex++
60             check(_events.isNotEmpty()) {
61                 "Failed to get next event for $this, there have been no interactions."
62             }
63             check(eventIdx < _events.size) {
64                 "Failed to get next event. Last event was ${events[eventIdx - 1]}"
65             }
66             return events[eventIdx]
67         }
68     }
69 
clearEventsnull70     public fun clearEvents() {
71         synchronized(lock) {
72             _events.clear()
73             nextEventIndex = 0
74         }
75     }
76 
77     public var rejectBuild: Boolean = false
<lambda>null78         get() = synchronized(lock) { field }
<lambda>null79         set(value) = synchronized(lock) { field = value }
80 
81     public var rejectSubmit: Boolean = false
<lambda>null82         get() = synchronized(lock) { field }
<lambda>null83         set(value) = synchronized(lock) { field = value }
84 
85     public var surfaceMap: Map<StreamId, Surface> = emptyMap()
<lambda>null86         get() = synchronized(lock) { field }
87         set(value) =
<lambda>null88             synchronized(lock) {
89                 field = value
90                 println("Configured surfaceMap for $this")
91             }
92 
93     @Volatile public var throwOnBuild: Boolean = false
94 
95     @Volatile public var throwOnSubmit: Boolean = false
96 
97     @Volatile public var throwOnStop: Boolean = false
98 
99     @Volatile public var throwOnAbort: Boolean = false
100 
101     @Volatile public var throwOnShutdown: Boolean = false
102 
buildnull103     override fun build(
104         isRepeating: Boolean,
105         requests: List<Request>,
106         defaultParameters: Map<*, Any?>,
107         graphParameters: Map<*, Any?>,
108         requiredParameters: Map<*, Any?>,
109         sequenceListener: CaptureSequenceListener,
110         listeners: List<Request.Listener>
111     ): FakeCaptureSequence? {
112         throwTestExceptionIf(throwOnBuild)
113 
114         val captureSequence =
115             FakeCaptureSequence.create(
116                 cameraId,
117                 isRepeating,
118                 requests,
119                 surfaceMap,
120                 defaultTemplate,
121                 defaultParameters,
122                 requiredParameters,
123                 graphParameters,
124                 listeners,
125                 sequenceListener
126             )
127         synchronized(lock) {
128             if (rejectBuild || shutdown || captureSequence == null) {
129                 println("$this: BuildRejected $captureSequence")
130                 _events.add(BuildRejected(captureSequence))
131                 return null
132             }
133         }
134         println("$this: Build $captureSequence")
135         return captureSequence
136     }
137 
submitnull138     override fun submit(captureSequence: FakeCaptureSequence): Int {
139         throwTestExceptionIf(throwOnSubmit)
140         synchronized(lock) {
141             if (rejectSubmit || shutdown) {
142                 println("$this: SubmitRejected $captureSequence")
143                 _events.add(SubmitRejected(captureSequence))
144                 return -1
145             }
146             captureQueue.add(captureSequence)
147             if (captureSequence.repeating) {
148                 repeatingCapture = captureSequence
149             }
150             println("$this: Submit $captureSequence")
151             _events.add(Submit(captureSequence))
152             return sequenceIds.incrementAndGet()
153         }
154     }
155 
abortCapturesnull156     override fun abortCaptures() {
157         throwTestExceptionIf(throwOnAbort)
158 
159         val requestSequencesToAbort: List<FakeCaptureSequence>
160         synchronized(lock) {
161             println("$this: AbortCaptures")
162             _events.add(AbortCaptures)
163             requestSequencesToAbort = captureQueue.toList()
164             captureQueue.clear()
165         }
166 
167         for (sequence in requestSequencesToAbort) {
168             sequence.invokeOnSequenceAborted()
169         }
170     }
171 
stopRepeatingnull172     override fun stopRepeating() {
173         throwTestExceptionIf(throwOnStop)
174         synchronized(lock) {
175             println("$this: StopRepeating")
176             _events.add(StopRepeating)
177             repeatingCapture = null
178         }
179     }
180 
shutdownnull181     override suspend fun shutdown() {
182         throwTestExceptionIf(throwOnShutdown)
183         synchronized(lock) {
184             println("$this: Shutdown")
185             shutdown = true
186             _events.add(Shutdown)
187         }
188     }
189 
toStringnull190     override fun toString(): String {
191         return "FakeCaptureSequenceProcessor-$debugId($cameraId)"
192     }
193 
194     /**
195      * Get the next CaptureSequence from this CaptureSequenceProcessor. If there are non-repeating
196      * capture requests in the queue, remove the first item from the queue. Otherwise, return the
197      * current repeating CaptureSequence, or null if there are no active CaptureSequences.
198      */
nextCaptureSequencenull199     internal fun nextCaptureSequence(): FakeCaptureSequence? =
200         synchronized(lock) { captureQueue.removeFirstOrNull() ?: repeatingCapture }
201 
throwTestExceptionIfnull202     private fun throwTestExceptionIf(condition: Boolean) {
203         if (condition) {
204             throw RuntimeException("Test Exception")
205         }
206     }
207 
208     public sealed interface Event
209 
210     public object Shutdown : Event
211 
212     public object StopRepeating : Event
213 
214     public object AbortCaptures : Event
215 
216     public data class BuildRejected(val captureSequence: FakeCaptureSequence?) : Event
217 
218     public data class SubmitRejected(val captureSequence: FakeCaptureSequence) : Event
219 
220     public data class Submit(val captureSequence: FakeCaptureSequence) : Event
221 
222     public companion object {
223         private val debugIds = atomic(0)
224         public val Event.requests: List<Request>
225             get() = checkNotNull(captureSequence).captureRequestList
226 
227         public val Event.requiredParameters: Map<*, Any?>
228             get() = checkNotNull(captureSequence).requiredParameters
229 
230         public val Event.defaultParameters: Map<*, Any?>
231             get() = checkNotNull(captureSequence).defaultParameters
232 
233         public val Event.graphParameters: Map<*, Any?>
234             get() = checkNotNull(captureSequence).graphParameters
235 
236         public val Event.isRepeating: Boolean
237             get() =
238                 when (this) {
239                     is Submit -> captureSequence.repeating == true
240                     is SubmitRejected -> captureSequence.repeating == true
241                     is BuildRejected -> captureSequence?.repeating == true
242                     else -> false
243                 }
244 
245         public val Event.isCapture: Boolean
246             get() =
247                 when (this) {
248                     is Submit -> captureSequence.repeating == false
249                     is SubmitRejected -> captureSequence.repeating == false
250                     is BuildRejected -> captureSequence?.repeating == false
251                     else -> false
252                 }
253 
254         public val Event.isRejected: Boolean
255             get() =
256                 when (this) {
257                     is BuildRejected,
258                     is SubmitRejected -> true
259                     else -> false
260                 }
261 
262         public val Event.isAbort: Boolean
263             get() = this is AbortCaptures
264 
265         public val Event.isStopRepeating: Boolean
266             get() = this is StopRepeating
267 
268         public val Event.isClose: Boolean
269             get() = this is Shutdown
270 
271         public val Event.captureSequence: FakeCaptureSequence?
272             get() =
273                 when (this) {
274                     is Submit -> captureSequence
275                     is BuildRejected -> captureSequence
276                     is SubmitRejected -> captureSequence
277                     else -> null
278                 }
279     }
280 }
281