1 /*
2  * Copyright 2019 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 package androidx.camera.core;
17 
18 import static java.lang.annotation.ElementType.FIELD;
19 import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
20 import static java.lang.annotation.ElementType.PARAMETER;
21 import static java.lang.annotation.ElementType.TYPE;
22 import static java.lang.annotation.ElementType.TYPE_USE;
23 
24 import android.hardware.camera2.params.SessionConfiguration;
25 
26 import androidx.annotation.IntDef;
27 import androidx.annotation.OptIn;
28 import androidx.annotation.RestrictTo;
29 import androidx.annotation.RestrictTo.Scope;
30 import androidx.camera.core.impl.CameraInfoInternal;
31 import androidx.camera.core.impl.CameraInternal;
32 import androidx.camera.core.impl.LensFacingCameraFilter;
33 import androidx.core.util.Preconditions;
34 
35 import org.jspecify.annotations.NonNull;
36 import org.jspecify.annotations.Nullable;
37 
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 import java.lang.annotation.Target;
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.Iterator;
44 import java.util.LinkedHashSet;
45 import java.util.List;
46 import java.util.Set;
47 
48 /**
49  * A set of requirements and priorities used to select a camera or return a filtered set of
50  * cameras.
51  */
52 public final class CameraSelector {
53 
54     /** A camera on the devices that its lens facing is resolved. */
55     public static final int LENS_FACING_UNKNOWN = -1;
56     /** A camera on the device facing the same direction as the device's screen. */
57     public static final int LENS_FACING_FRONT = 0;
58     /** A camera on the device facing the opposite direction as the device's screen. */
59     public static final int LENS_FACING_BACK = 1;
60     /**
61      * An external camera that has no fixed facing relative to the device's screen.
62      *
63      * <p>The behavior of an external camera highly depends on the manufacturer. Currently it's
64      * treated similar to a front facing camera with little verification. So it's considered
65      * experimental and should be used with caution.
66      */
67     @ExperimentalLensFacing
68     public static final int LENS_FACING_EXTERNAL = 2;
69 
70     /** A static {@link CameraSelector} that selects the default front facing camera. */
71     public static final @NonNull CameraSelector DEFAULT_FRONT_CAMERA =
72             new CameraSelector.Builder().requireLensFacing(LENS_FACING_FRONT).build();
73     /** A static {@link CameraSelector} that selects the default back facing camera. */
74     public static final @NonNull CameraSelector DEFAULT_BACK_CAMERA =
75             new CameraSelector.Builder().requireLensFacing(LENS_FACING_BACK).build();
76 
77     private final @NonNull LinkedHashSet<CameraFilter> mCameraFilterSet;
78 
79     private final @Nullable String mPhysicalCameraId;
80 
CameraSelector(@onNull LinkedHashSet<CameraFilter> cameraFilterSet, @Nullable String physicalCameraId)81     CameraSelector(@NonNull LinkedHashSet<CameraFilter> cameraFilterSet,
82             @Nullable String physicalCameraId) {
83         mCameraFilterSet = cameraFilterSet;
84         mPhysicalCameraId = physicalCameraId;
85     }
86 
87     /**
88      * Selects the first camera that filtered by the {@link CameraFilter}s assigned to this
89      * {@link CameraSelector}.
90      *
91      * <p>When filtering with {@link CameraFilter}, the output set must be contained in the input
92      * set, otherwise an IllegalArgumentException will be thrown.
93      *
94      * @param cameras The camera set being filtered.
95      * @return The first camera filtered.
96      * @throws IllegalArgumentException If there's no available camera after filtering or the
97      *                                  filtered cameras aren't contained in the input set.
98      */
99     @RestrictTo(Scope.LIBRARY_GROUP)
select(@onNull LinkedHashSet<CameraInternal> cameras)100     public @NonNull CameraInternal select(@NonNull LinkedHashSet<CameraInternal> cameras) {
101         Iterator<CameraInternal> cameraInternalIterator = filter(cameras).iterator();
102         if (cameraInternalIterator.hasNext()) {
103             return cameraInternalIterator.next();
104         } else {
105             String errorMessage = String.format(
106                     "No available camera can be found. %s %s", logCameras(cameras), logSelector());
107             throw new IllegalArgumentException(errorMessage);
108         }
109     }
110 
logCameras(@onNull Set<CameraInternal> cameras)111     private String logCameras(@NonNull Set<CameraInternal> cameras) {
112         StringBuilder sb = new StringBuilder();
113         sb.append("Cams:").append(cameras.size());
114         for (CameraInternal camera : cameras) {
115             CameraInfoInternal info = camera.getCameraInfoInternal();
116             sb.append(String.format(" Id:%s  Lens:%s", info.getCameraId(), info.getLensFacing()));
117         }
118         return sb.toString();
119     }
120 
logSelector()121     private String logSelector() {
122         StringBuilder sb = new StringBuilder();
123         sb.append(
124                 String.format("PhyId:%s  Filters:%s", mPhysicalCameraId, mCameraFilterSet.size()));
125         for (CameraFilter filter : mCameraFilterSet) {
126             sb.append(" Id:").append(filter.getIdentifier());
127             if (filter instanceof LensFacingCameraFilter) {
128                 sb.append(" LensFilter:").append(
129                         ((LensFacingCameraFilter) filter).getLensFacing());
130             }
131         }
132         return sb.toString();
133     }
134 
135     /**
136      * Filters the input {@link CameraInfo}s using the {@link CameraFilter}s assigned to the
137      * selector.
138      *
139      * <p>If the {@link CameraFilter}s assigned to this selector produce a camera info that
140      * is not part of the input list, the output list will be empty.
141      *
142      * <p>An example use case for using this function is when you want to get all
143      * {@link CameraInfo}s for all available back facing cameras.
144      * <pre>
145      * eg.
146      * {@code
147      * CameraInfo defaultBackCameraInfo = null;
148      * CameraSelector selector = new CameraSelector.Builder()
149      *      .requireLensFacing(LENS_FACING_BACK).build();
150      * List<CameraInfo> cameraInfos = selector.filter(cameraProvider.getAvailableCameraInfos());
151      * }
152      * </pre>
153      *
154      * @param cameraInfos The camera infos list being filtered.
155      * @return The remaining list of camera infos.
156      * @throws UnsupportedOperationException If the {@link CameraFilter}s assigned to the selector
157      *                                       try to modify the input camera infos list.
158      * @throws IllegalArgumentException If the device cannot return the necessary information for
159      *                                  filtering, it will throw this exception.
160      */
filter(@onNull List<CameraInfo> cameraInfos)161     public @NonNull List<CameraInfo> filter(@NonNull List<CameraInfo> cameraInfos) {
162         List<CameraInfo> output = new ArrayList<>(cameraInfos);
163         for (CameraFilter filter : mCameraFilterSet) {
164             output = filter.filter(Collections.unmodifiableList(output));
165         }
166 
167         output.retainAll(cameraInfos);
168         return output;
169     }
170 
171     /**
172      * Filters the input cameras using the {@link CameraFilter} assigned to the selector.
173      *
174      * <p>The cameras filtered must be contained in the input set. Otherwise it will throw an
175      * exception.
176      *
177      * @param cameras The camera set being filtered.
178      * @return The remaining set of cameras.
179      *
180      */
181     @RestrictTo(Scope.LIBRARY_GROUP)
filter( @onNull LinkedHashSet<CameraInternal> cameras)182     public @NonNull LinkedHashSet<CameraInternal> filter(
183             @NonNull LinkedHashSet<CameraInternal> cameras) {
184         List<CameraInfo> input = new ArrayList<>();
185         for (CameraInternal camera : cameras) {
186             input.add(camera.getCameraInfo());
187         }
188 
189         List<CameraInfo> result = filter(input);
190 
191         LinkedHashSet<CameraInternal> output = new LinkedHashSet<>();
192         for (CameraInternal camera : cameras) {
193             if (result.contains(camera.getCameraInfo())) {
194                 output.add(camera);
195             }
196         }
197 
198         return output;
199     }
200 
201     /**
202      * Gets the set of {@link CameraFilter} assigned to this camera selector.
203      *
204      */
205     @RestrictTo(Scope.LIBRARY_GROUP)
getCameraFilterSet()206     public @NonNull LinkedHashSet<CameraFilter> getCameraFilterSet() {
207         return mCameraFilterSet;
208     }
209 
210     /**
211      * Returns a single lens facing from this camera selector, or null if lens facing has not
212      * been set.
213      *
214      * @return The lens facing.
215      * @throws IllegalStateException if a single lens facing cannot be resolved, such as if
216      *                               multiple conflicting lens facing requirements exist in this
217      *                               camera selector.
218      */
219     @RestrictTo(Scope.LIBRARY_GROUP)
getLensFacing()220     public @Nullable Integer getLensFacing() {
221         Integer currentLensFacing = null;
222         for (CameraFilter filter : mCameraFilterSet) {
223             if (filter instanceof LensFacingCameraFilter) {
224                 Integer newLensFacing = ((LensFacingCameraFilter) filter).getLensFacing();
225                 if (currentLensFacing == null) {
226                     currentLensFacing = newLensFacing;
227                 } else if (!currentLensFacing.equals(newLensFacing)) {
228                     // TODO(b/122975195): Now we assume the lens facing of a camera is either
229                     //  FRONT or BACK, so if there's conflicting lens facings set, throws an
230                     //  exception. It needs to be revisited if we have a third lens facing enum
231                     //  in the future.
232                     throw new IllegalStateException(
233                             "Multiple conflicting lens facing requirements exist.");
234                 }
235             }
236         }
237 
238         return currentLensFacing;
239     }
240 
241     /**
242      * Returns the physical camera id.
243      *
244      * <p>If physical camera id is not set via {@link Builder#setPhysicalCameraId(String)},
245      * it will return null.
246      *
247      * @return physical camera id.
248      * @see Builder#setPhysicalCameraId(String)
249      */
getPhysicalCameraId()250     public @Nullable String getPhysicalCameraId() {
251         return mPhysicalCameraId;
252     }
253 
254     /** Builder for a {@link CameraSelector}. */
255     public static final class Builder {
256         private final @NonNull LinkedHashSet<CameraFilter> mCameraFilterSet;
257 
258         private @Nullable String mPhysicalCameraId;
259 
Builder()260         public Builder() {
261             mCameraFilterSet = new LinkedHashSet<>();
262         }
263 
Builder(@onNull LinkedHashSet<CameraFilter> cameraFilterSet)264         private Builder(@NonNull LinkedHashSet<CameraFilter> cameraFilterSet) {
265             mCameraFilterSet = new LinkedHashSet<>(cameraFilterSet);
266         }
267 
268         /**
269          * Requires a camera with the specified lens facing.
270          *
271          * <p>Valid values for lens facing are {@link CameraSelector#LENS_FACING_FRONT},
272          * {@link CameraSelector#LENS_FACING_BACK} and
273          * {@link CameraSelector#LENS_FACING_EXTERNAL}. However, requiring
274          * {@link CameraSelector#LENS_FACING_EXTERNAL} is currently experimental and may produce
275          * unexpected behaviors.
276          *
277          * <p>If lens facing is already set, this will add extra requirement for lens facing
278          * instead of replacing the previous setting.
279          *
280          * @param lensFacing the lens facing for selecting cameras with.
281          * @return this builder.
282          */
requireLensFacing(@ensFacing int lensFacing)283         public @NonNull Builder requireLensFacing(@LensFacing int lensFacing) {
284             Preconditions.checkState(lensFacing != LENS_FACING_UNKNOWN, "The specified lens "
285                     + "facing is invalid.");
286             mCameraFilterSet.add(new LensFacingCameraFilter(lensFacing));
287             return this;
288         }
289 
290         /**
291          * Adds a {@link CameraFilter} to the current set of filters. It can be used to select a
292          * specific camera based on customized criteria like Camera2 characteristics.
293          *
294          * <p>Multiple filters can be added. All filters will be applied by the order they were
295          * added when the {@link CameraSelector} is used, and the first camera output from the
296          * filters will be selected.
297          *
298          * @param cameraFilter the {@link CameraFilter} for selecting cameras with.
299          * @return this builder.
300          */
addCameraFilter(@onNull CameraFilter cameraFilter)301         public @NonNull Builder addCameraFilter(@NonNull CameraFilter cameraFilter) {
302             mCameraFilterSet.add(cameraFilter);
303             return this;
304         }
305 
306         /**
307          * Generates a Builder from another CameraSelector object.
308          *
309          * @param cameraSelector An existing CameraSelector.
310          * @return The new Builder.
311          */
312         @RestrictTo(Scope.LIBRARY_GROUP)
fromSelector(@onNull CameraSelector cameraSelector)313         public static @NonNull Builder fromSelector(@NonNull CameraSelector cameraSelector) {
314             CameraSelector.Builder builder = new CameraSelector.Builder(
315                     cameraSelector.getCameraFilterSet());
316             return builder;
317         }
318 
319         /**
320          * Sets the physical camera id.
321          *
322          * <p>A logical camera is a grouping of two or more of those physical cameras.
323          * See <a href="https://developer.android.com/media/camera/camera2/multi-camera">Multi-camera API</a>
324          *
325          * <p> If we want to open one physical camera, for example ultra wide, we just need to set
326          * physical camera id in {@link CameraSelector} and bind to lifecycle. All CameraX features
327          * will work normally when only a single physical camera is used.
328          *
329          * <p>If we want to open multiple physical cameras, we need to have multiple
330          * {@link CameraSelector}s and set physical camera id on each, then bind to lifecycle with
331          * the {@link CameraSelector}s. Internally each physical camera id will be set on
332          * {@link UseCase}, for example, {@link Preview} and call
333          * {@link android.hardware.camera2.params.OutputConfiguration#setPhysicalCameraId(String)}.
334          *
335          * <p>Currently only two physical cameras for the same logical camera id are allowed
336          * and the device needs to support physical cameras by checking
337          * {@link CameraInfo#isLogicalMultiCameraSupported()}. In addition, there is no guarantee
338          * or API to query whether the device supports multiple physical camera opening or not.
339          * Internally the library checks
340          * {@link android.hardware.camera2.CameraDevice#isSessionConfigurationSupported(SessionConfiguration)},
341          * if the device does not support the multiple physical camera configuration,
342          * {@link IllegalArgumentException} will be thrown when binding to lifecycle.
343          *
344          * @param physicalCameraId physical camera id.
345          * @return this builder.
346          */
setPhysicalCameraId(@onNull String physicalCameraId)347         public @NonNull Builder setPhysicalCameraId(@NonNull String physicalCameraId) {
348             mPhysicalCameraId = physicalCameraId;
349             return this;
350         }
351 
352         /** Builds the {@link CameraSelector}. */
build()353         public @NonNull CameraSelector build() {
354             return new CameraSelector(mCameraFilterSet, mPhysicalCameraId);
355         }
356     }
357 
358     /**
359      * The direction the camera faces relative to device screen.
360      *
361      */
362     @Target({TYPE, TYPE_USE, FIELD, PARAMETER, LOCAL_VARIABLE})
363     @OptIn(markerClass = ExperimentalLensFacing.class)
364     @IntDef({LENS_FACING_UNKNOWN, LENS_FACING_FRONT, LENS_FACING_BACK, LENS_FACING_EXTERNAL})
365     @Retention(RetentionPolicy.SOURCE)
366     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
367     public @interface LensFacing {
368     }
369 }
370