1 /*
2 * Copyright 2020 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 @file:Suppress("NOTHING_TO_INLINE")
18
19 package androidx.camera.camera2.pipe
20
21 import androidx.annotation.RestrictTo
22 import kotlinx.coroutines.Deferred
23 import kotlinx.coroutines.flow.Flow
24 import kotlinx.coroutines.flow.flow
25
26 @JvmDefaultWithCompatibility
27 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
28 /** Methods for querying, iterating, and selecting the Cameras that are available on the device. */
29 public interface CameraDevices {
30 /**
31 * A flow of the list of currently openable [CameraId]s from the provided CameraBackend. It
32 * should continuously return a list of current cameras, and the list should be updated camera
33 * availability changes, e.g., an external camera is plugged or unplugged. The flow should also
34 * replay the most recent value for each new subscriber.
35 */
cameraIdsFlownull36 public fun cameraIdsFlow(cameraBackendId: CameraBackendId? = null): Flow<List<CameraId>>
37
38 /**
39 * Read the list of currently openable [CameraId]s from the provided CameraBackend, suspending
40 * if needed. By default this will load the list of openable [CameraId]s from the default
41 * backend.
42 */
43 public suspend fun getCameraIds(cameraBackendId: CameraBackendId? = null): List<CameraId>?
44
45 /**
46 * Read the list of currently openable [CameraId]s from the provided CameraBackend, blocking the
47 * thread if needed. By default this will load the list of openable [CameraId]s from the default
48 * backend.
49 */
50 public fun awaitCameraIds(cameraBackendId: CameraBackendId? = null): List<CameraId>?
51
52 /**
53 * Read the set of [CameraId] sets that can be operated concurrently from the provided
54 * CameraBackend, suspending if needed. By default this will load the set of [CameraId] sets
55 * from the default backend.
56 */
57 public suspend fun getConcurrentCameraIds(
58 cameraBackendId: CameraBackendId? = null
59 ): Set<Set<CameraId>>?
60
61 /**
62 * Read the set of [CameraId] sets that can be operated concurrently from the provided
63 * CameraBackend, blocking the thread if needed. By default this will load the set of [CameraId]
64 * sets from the default backend.
65 */
66 public fun awaitConcurrentCameraIds(
67 cameraBackendId: CameraBackendId? = null
68 ): Set<Set<CameraId>>?
69
70 /**
71 * Read metadata for a specific camera id, suspending if needed. By default, this method will
72 * query metadata from the default backend if one is not specified.
73 */
74 public suspend fun getCameraMetadata(
75 cameraId: CameraId,
76 cameraBackendId: CameraBackendId? = null
77 ): CameraMetadata?
78
79 /**
80 * Read metadata for a specific camera id, blocking if needed. By default, this method will
81 * query metadata from the default backend if one is not specified.
82 */
83 public fun awaitCameraMetadata(
84 cameraId: CameraId,
85 cameraBackendId: CameraBackendId? = null
86 ): CameraMetadata?
87
88 /**
89 * Opens the camera device indicated by the cameraId, so that any subsequent open calls will
90 * potentially have a better latency.
91 */
92 public fun prewarm(cameraId: CameraId, cameraBackendId: CameraBackendId? = null)
93
94 /** Non blocking operation that disconnects the underlying active Camera. */
95 public fun disconnect(cameraId: CameraId, cameraBackendId: CameraBackendId? = null)
96
97 /**
98 * Disconnects the underlying active Camera. Once fully closed, the returned [Deferred] should
99 * be completed. It is synchronous with the other operations within this class.
100 */
101 public fun disconnectAsync(
102 cameraId: CameraId,
103 cameraBackendId: CameraBackendId? = null
104 ): Deferred<Unit>
105
106 /** Non blocking operation that disconnects all active Cameras. */
107 public fun disconnectAll(cameraBackendId: CameraBackendId? = null)
108
109 /**
110 * Non blocking operation that disconnects all active Cameras. Once all connections are fully
111 * closed, the returned [Deferred] should be completed. It is synchronous with the other
112 * operations within this class.
113 */
114 public fun disconnectAllAsync(cameraBackendId: CameraBackendId? = null): Deferred<Unit>
115
116 /**
117 * Iterate and return a list of CameraId's on the device that are capable of being opened. Some
118 * camera devices may be hidden or un-openable if they are included as part of a logical camera
119 * group.
120 */
121 @Deprecated(
122 message = "findAll() is not able to specify a specific CameraBackendId to query.",
123 replaceWith = ReplaceWith("awaitCameraIds"),
124 level = DeprecationLevel.WARNING
125 )
126 public fun findAll(): List<CameraId>
127
128 /**
129 * Load the list of CameraIds from the Camera2 CameraManager, suspending if the list of
130 * CameraIds has not yet been loaded.
131 */
132 @Deprecated(
133 message = "ids() is not able to specify a specific CameraBackendId to query.",
134 replaceWith = ReplaceWith("getCameraIds"),
135 level = DeprecationLevel.WARNING
136 )
137 public suspend fun ids(): List<CameraId>
138
139 /**
140 * Load CameraMetadata for a specific CameraId. Loading CameraMetadata can take a non-zero
141 * amount of time to execute. If CameraMetadata is not already cached this function will suspend
142 * until CameraMetadata can be loaded.
143 */
144 @Deprecated(
145 message = "getMetadata() is not able to specify a specific CameraBackendId to query.",
146 replaceWith = ReplaceWith("getCameraMetadata"),
147 level = DeprecationLevel.WARNING
148 )
149 public suspend fun getMetadata(camera: CameraId): CameraMetadata
150
151 /**
152 * Load CameraMetadata for a specific CameraId and block the calling thread until the result is
153 * available.
154 */
155 @Deprecated(
156 message = "awaitMetadata() is not able to specify a specific CameraBackendId to query.",
157 replaceWith = ReplaceWith("awaitCameraMetadata"),
158 level = DeprecationLevel.WARNING
159 )
160 public fun awaitMetadata(camera: CameraId): CameraMetadata
161 }
162
163 /** CameraId represents a typed identifier for a camera represented as a non-blank String. */
164 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
165 @JvmInline
166 public value class CameraId(public val value: String) {
167 init {
168 require(value.isNotBlank()) { "CameraId cannot be null or blank!" }
169 }
170
171 public companion object {
172 public inline fun fromCamera2Id(value: String): CameraId = CameraId(value)
173
174 public inline fun fromCamera1Id(value: Int): CameraId = CameraId("$value")
175 }
176
177 /**
178 * Attempt to parse an camera1 id from a camera2 id.
179 *
180 * @return The parsed Camera1 id, or null if the value cannot be parsed as a Camera1 id.
181 */
182 public inline fun toCamera1Id(): Int? = value.toIntOrNull()
183
184 override fun toString(): String = "CameraId-$value"
185 }
186
187 /**
188 * Produce a [Flow]<[CameraMetadata]>, optionally expanding the list to include the physical
189 * metadata of cameras that are otherwise hidden. Metadata for hidden cameras are always returned
190 * last.
191 */
192 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
findnull193 public fun CameraDevices.find(
194 cameraBackendId: CameraBackendId? = null,
195 includePhysicalCameraMetadata: Boolean = false
196 ): Flow<CameraMetadata> = flow {
197 val cameraIds = this@find.getCameraIds() ?: return@flow
198
199 val visited = mutableSetOf<CameraId>()
200 val emitted = mutableSetOf<CameraMetadata>()
201 for (cameraId in cameraIds) {
202 if (visited.add(cameraId)) {
203 val metadata = this@find.getCameraMetadata(cameraId, cameraBackendId)
204 if (metadata != null) {
205 emitted.add(metadata)
206 emit(metadata)
207 }
208 }
209 }
210
211 if (includePhysicalCameraMetadata) {
212 for (metadata in emitted) {
213 for (physicalId in metadata.physicalCameraIds) {
214 if (!visited.contains(physicalId)) {
215 val physicalMetadata = this@find.getCameraMetadata(physicalId, cameraBackendId)
216 if (
217 physicalMetadata != null &&
218 physicalMetadata.camera == physicalId &&
219 visited.add(physicalId)
220 ) {
221 emit(physicalMetadata)
222 }
223 }
224 }
225 }
226 }
227 }
228