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