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