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