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 
17 package androidx.camera.camera2.internal;
18 
19 import static android.hardware.camera2.CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES;
20 import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON;
21 import static android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION;
22 import static android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO;
23 import static android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA;
24 import static android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING;
25 import static android.hardware.camera2.CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME;
26 import static android.hardware.camera2.CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN;
27 
28 import static androidx.camera.camera2.internal.ZslUtil.isCapabilitySupported;
29 
30 import android.annotation.SuppressLint;
31 import android.hardware.camera2.CameraCharacteristics;
32 import android.hardware.camera2.CameraMetadata;
33 import android.os.Build;
34 import android.util.Pair;
35 import android.util.Range;
36 import android.util.Size;
37 import android.view.Surface;
38 
39 import androidx.annotation.FloatRange;
40 import androidx.annotation.GuardedBy;
41 import androidx.annotation.IntRange;
42 import androidx.annotation.OptIn;
43 import androidx.camera.camera2.internal.compat.CameraAccessExceptionCompat;
44 import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
45 import androidx.camera.camera2.internal.compat.CameraManagerCompat;
46 import androidx.camera.camera2.internal.compat.StreamConfigurationMapCompat;
47 import androidx.camera.camera2.internal.compat.params.DynamicRangesCompat;
48 import androidx.camera.camera2.internal.compat.quirk.CameraQuirks;
49 import androidx.camera.camera2.internal.compat.quirk.DeviceQuirks;
50 import androidx.camera.camera2.internal.compat.quirk.ZslDisablerQuirk;
51 import androidx.camera.camera2.internal.compat.workaround.FlashAvailabilityChecker;
52 import androidx.camera.camera2.interop.Camera2CameraInfo;
53 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
54 import androidx.camera.core.CameraInfo;
55 import androidx.camera.core.CameraSelector;
56 import androidx.camera.core.CameraState;
57 import androidx.camera.core.DynamicRange;
58 import androidx.camera.core.ExposureState;
59 import androidx.camera.core.FocusMeteringAction;
60 import androidx.camera.core.Logger;
61 import androidx.camera.core.ZoomState;
62 import androidx.camera.core.impl.CameraCaptureCallback;
63 import androidx.camera.core.impl.CameraInfoInternal;
64 import androidx.camera.core.impl.DynamicRanges;
65 import androidx.camera.core.impl.EncoderProfilesProvider;
66 import androidx.camera.core.impl.ImageOutputConfig.RotationValue;
67 import androidx.camera.core.impl.Quirks;
68 import androidx.camera.core.impl.Timebase;
69 import androidx.camera.core.impl.utils.CameraOrientationUtil;
70 import androidx.camera.core.impl.utils.RedirectableLiveData;
71 import androidx.core.util.Preconditions;
72 import androidx.lifecycle.LiveData;
73 
74 import org.jspecify.annotations.NonNull;
75 import org.jspecify.annotations.Nullable;
76 
77 import java.util.ArrayList;
78 import java.util.Arrays;
79 import java.util.Collections;
80 import java.util.HashSet;
81 import java.util.Iterator;
82 import java.util.LinkedHashMap;
83 import java.util.List;
84 import java.util.Map;
85 import java.util.Objects;
86 import java.util.Set;
87 import java.util.concurrent.Executor;
88 
89 /**
90  * Implementation of the {@link CameraInfoInternal} interface that exposes parameters through
91  * camera2.
92  *
93  * <p>Construction consists of two stages. The constructor creates a implementation without a
94  * {@link Camera2CameraControlImpl} and will return default values for camera control related
95  * states like zoom/exposure/torch. After {@link #linkWithCameraControl} is called,
96  * zoom/exposure/torch API will reflect the states in the {@link Camera2CameraControlImpl}. Any
97  * CameraCaptureCallbacks added before this link will also be added
98  * to the {@link Camera2CameraControlImpl}.
99  */
100 @OptIn(markerClass = ExperimentalCamera2Interop.class)
101 public final class Camera2CameraInfoImpl implements CameraInfoInternal {
102 
103     private static final String TAG = "Camera2CameraInfo";
104     private final String mCameraId;
105     private final CameraCharacteristicsCompat mCameraCharacteristicsCompat;
106     private final Camera2CameraInfo mCamera2CameraInfo;
107 
108     private final Object mLock = new Object();
109     @GuardedBy("mLock")
110     private @Nullable Camera2CameraControlImpl mCamera2CameraControlImpl;
111     @GuardedBy("mLock")
112     private @Nullable RedirectableLiveData<Integer> mRedirectTorchStateLiveData = null;
113     @GuardedBy("mLock")
114     private @Nullable RedirectableLiveData<Integer> mRedirectTorchStrengthLiveData = null;
115     @GuardedBy("mLock")
116     private @Nullable RedirectableLiveData<Integer> mRedirectLowLightBoostStateLiveData = null;
117     @GuardedBy("mLock")
118     private @Nullable RedirectableLiveData<ZoomState> mRedirectZoomStateLiveData = null;
119     private final @NonNull RedirectableLiveData<CameraState> mCameraStateLiveData;
120     @GuardedBy("mLock")
121     private @Nullable List<Pair<CameraCaptureCallback, Executor>> mCameraCaptureCallbacks = null;
122 
123     private final @NonNull Quirks mCameraQuirks;
124     private final @NonNull EncoderProfilesProvider mCamera2EncoderProfilesProvider;
125     private final @NonNull CameraManagerCompat mCameraManager;
126 
127     private @Nullable Set<CameraInfo> mPhysicalCameraInfos;
128 
129     /**
130      * Constructs an instance. Before {@link #linkWithCameraControl(Camera2CameraControlImpl)} is
131      * called, camera control related API (torch/exposure/zoom) will return default values.
132      */
Camera2CameraInfoImpl(@onNull String cameraId, @NonNull CameraManagerCompat cameraManager)133     public Camera2CameraInfoImpl(@NonNull String cameraId,
134             @NonNull CameraManagerCompat cameraManager) throws CameraAccessExceptionCompat {
135         mCameraId = Preconditions.checkNotNull(cameraId);
136         mCameraManager = cameraManager;
137 
138         mCameraCharacteristicsCompat = cameraManager.getCameraCharacteristicsCompat(mCameraId);
139         mCamera2CameraInfo = new Camera2CameraInfo(this);
140         mCameraQuirks = CameraQuirks.get(cameraId, mCameraCharacteristicsCompat);
141         mCamera2EncoderProfilesProvider = new Camera2EncoderProfilesProvider(cameraId,
142                 mCameraQuirks);
143         mCameraStateLiveData = new RedirectableLiveData<>(
144                 CameraState.create(CameraState.Type.CLOSED));
145     }
146 
147     /**
148      * Links with a {@link Camera2CameraControlImpl}. After the link, zoom/torch/exposure
149      * operations of CameraControl will modify the states in this Camera2CameraInfoImpl.
150      * Also, any CameraCaptureCallbacks added before this link will be added to the
151      * {@link Camera2CameraControlImpl}.
152      */
linkWithCameraControl(@onNull Camera2CameraControlImpl camera2CameraControlImpl)153     void linkWithCameraControl(@NonNull Camera2CameraControlImpl camera2CameraControlImpl) {
154         synchronized (mLock) {
155             mCamera2CameraControlImpl = camera2CameraControlImpl;
156 
157             if (mRedirectZoomStateLiveData != null) {
158                 mRedirectZoomStateLiveData.redirectTo(
159                         mCamera2CameraControlImpl.getZoomControl().getZoomState());
160             }
161 
162             if (mRedirectTorchStateLiveData != null) {
163                 mRedirectTorchStateLiveData.redirectTo(
164                         mCamera2CameraControlImpl.getTorchControl().getTorchState());
165             }
166 
167             if (mRedirectTorchStrengthLiveData != null) {
168                 mRedirectTorchStrengthLiveData.redirectTo(
169                         mCamera2CameraControlImpl.getTorchControl().getTorchStrengthLevel());
170             }
171 
172             if (mRedirectLowLightBoostStateLiveData != null) {
173                 mRedirectLowLightBoostStateLiveData.redirectTo(mCamera2CameraControlImpl
174                         .getLowLightBoostControl().getLowLightBoostState());
175             }
176 
177             if (mCameraCaptureCallbacks != null) {
178                 for (Pair<CameraCaptureCallback, Executor> pair :
179                         mCameraCaptureCallbacks) {
180                     mCamera2CameraControlImpl.addSessionCameraCaptureCallback(pair.second,
181                             pair.first);
182                 }
183                 mCameraCaptureCallbacks = null;
184             }
185         }
186         logDeviceInfo();
187     }
188 
189     /**
190      * Sets the source of the {@linkplain CameraState camera states} that will be exposed. When
191      * called more than once, the previous camera state source is overridden.
192      */
setCameraStateSource(@onNull LiveData<CameraState> cameraStateSource)193     void setCameraStateSource(@NonNull LiveData<CameraState> cameraStateSource) {
194         mCameraStateLiveData.redirectTo(cameraStateSource);
195     }
196 
197     @Override
getCameraId()198     public @NonNull String getCameraId() {
199         return mCameraId;
200     }
201 
getCameraCharacteristicsCompat()202     public @NonNull CameraCharacteristicsCompat getCameraCharacteristicsCompat() {
203         return mCameraCharacteristicsCompat;
204     }
205 
206     @CameraSelector.LensFacing
207     @Override
getLensFacing()208     public int getLensFacing() {
209         Integer lensFacing = mCameraCharacteristicsCompat.get(CameraCharacteristics.LENS_FACING);
210         Preconditions.checkArgument(lensFacing != null, "Unable to get the lens facing of the "
211                 + "camera.");
212         return LensFacingUtil.getCameraSelectorLensFacing(lensFacing);
213     }
214 
215     @Override
getSensorRotationDegrees(@otationValue int relativeRotation)216     public int getSensorRotationDegrees(@RotationValue int relativeRotation) {
217         int sensorOrientation = getSensorOrientation();
218         int relativeRotationDegrees =
219                 CameraOrientationUtil.surfaceRotationToDegrees(relativeRotation);
220         // Currently this assumes that a back-facing camera is always opposite to the screen.
221         // This may not be the case for all devices, so in the future we may need to handle that
222         // scenario.
223         final int lensFacing = getLensFacing();
224         boolean isOppositeFacingScreen = CameraSelector.LENS_FACING_BACK == lensFacing;
225         return CameraOrientationUtil.getRelativeImageRotation(
226                 relativeRotationDegrees,
227                 sensorOrientation,
228                 isOppositeFacingScreen);
229     }
230 
getSensorOrientation()231     int getSensorOrientation() {
232         Integer sensorOrientation =
233                 mCameraCharacteristicsCompat.get(CameraCharacteristics.SENSOR_ORIENTATION);
234         Preconditions.checkNotNull(sensorOrientation);
235         return sensorOrientation;
236     }
237 
getSupportedHardwareLevel()238     int getSupportedHardwareLevel() {
239         Integer deviceLevel =
240                 mCameraCharacteristicsCompat.get(
241                         CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
242         Preconditions.checkNotNull(deviceLevel);
243         return deviceLevel;
244     }
245 
246     @Override
getSensorRotationDegrees()247     public int getSensorRotationDegrees() {
248         return getSensorRotationDegrees(Surface.ROTATION_0);
249     }
250 
logDeviceInfo()251     private void logDeviceInfo() {
252         // Extend by adding logging here as needed.
253         logDeviceLevel();
254     }
255 
logDeviceLevel()256     private void logDeviceLevel() {
257         String levelString;
258 
259         int deviceLevel = getSupportedHardwareLevel();
260         switch (deviceLevel) {
261             case CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY:
262                 levelString = "INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY";
263                 break;
264             case CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL:
265                 levelString = "INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL";
266                 break;
267             case CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED:
268                 levelString = "INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED";
269                 break;
270             case CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL:
271                 levelString = "INFO_SUPPORTED_HARDWARE_LEVEL_FULL";
272                 break;
273             case CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3:
274                 levelString = "INFO_SUPPORTED_HARDWARE_LEVEL_3";
275                 break;
276             default:
277                 levelString = "Unknown value: " + deviceLevel;
278                 break;
279         }
280         Logger.i(TAG, "Device Level: " + levelString);
281     }
282 
283     @Override
hasFlashUnit()284     public boolean hasFlashUnit() {
285         return FlashAvailabilityChecker.isFlashAvailable(mCameraCharacteristicsCompat::get);
286     }
287 
288     @Override
getTorchState()289     public @NonNull LiveData<Integer> getTorchState() {
290         synchronized (mLock) {
291             if (mCamera2CameraControlImpl == null) {
292                 if (mRedirectTorchStateLiveData == null) {
293                     mRedirectTorchStateLiveData =
294                             new RedirectableLiveData<>(TorchControl.DEFAULT_TORCH_STATE);
295                 }
296                 return mRedirectTorchStateLiveData;
297             }
298 
299             // if RedirectableLiveData exists,  use it directly.
300             if (mRedirectTorchStateLiveData != null) {
301                 return mRedirectTorchStateLiveData;
302             }
303 
304             return mCamera2CameraControlImpl.getTorchControl().getTorchState();
305         }
306     }
307 
308     @Override
isLowLightBoostSupported()309     public boolean isLowLightBoostSupported() {
310         return LowLightBoostControl.checkLowLightBoostAvailability(mCameraCharacteristicsCompat);
311     }
312 
313     @Override
getLowLightBoostState()314     public @NonNull LiveData<Integer> getLowLightBoostState() {
315         synchronized (mLock) {
316             if (mCamera2CameraControlImpl == null) {
317                 if (mRedirectLowLightBoostStateLiveData == null) {
318                     mRedirectLowLightBoostStateLiveData =
319                             new RedirectableLiveData<>(LowLightBoostControl.DEFAULT_LLB_STATE);
320                 }
321                 return mRedirectLowLightBoostStateLiveData;
322             }
323 
324             // if RedirectableLiveData exists,  use it directly.
325             if (mRedirectLowLightBoostStateLiveData != null) {
326                 return mRedirectLowLightBoostStateLiveData;
327             }
328 
329             return mCamera2CameraControlImpl.getLowLightBoostControl().getLowLightBoostState();
330         }
331     }
332 
333     @Override
getZoomState()334     public @NonNull LiveData<ZoomState> getZoomState() {
335         synchronized (mLock) {
336             if (mCamera2CameraControlImpl == null) {
337                 if (mRedirectZoomStateLiveData == null) {
338                     mRedirectZoomStateLiveData = new RedirectableLiveData<>(
339                             ZoomControl.getDefaultZoomState(mCameraCharacteristicsCompat));
340                 }
341                 return mRedirectZoomStateLiveData;
342             }
343 
344             // if RedirectableLiveData exists,  use it directly.
345             if (mRedirectZoomStateLiveData != null) {
346                 return mRedirectZoomStateLiveData;
347             }
348 
349             return mCamera2CameraControlImpl.getZoomControl().getZoomState();
350         }
351     }
352 
353     @Override
getExposureState()354     public @NonNull ExposureState getExposureState() {
355         synchronized (mLock) {
356             if (mCamera2CameraControlImpl == null) {
357                 return ExposureControl.getDefaultExposureState(mCameraCharacteristicsCompat);
358             }
359             return mCamera2CameraControlImpl.getExposureControl().getExposureState();
360         }
361     }
362 
363     @Override
getCameraState()364     public @NonNull LiveData<CameraState> getCameraState() {
365         return mCameraStateLiveData;
366     }
367 
368     /**
369      * {@inheritDoc}
370      *
371      * <p>When the CameraX configuration is {@link androidx.camera.camera2.Camera2Config}, the
372      * return value depends on whether the device is legacy
373      * ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL} {@code ==
374      * }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY}).
375      *
376      * @return {@link #IMPLEMENTATION_TYPE_CAMERA2_LEGACY} if the device is legacy, otherwise
377      * {@link #IMPLEMENTATION_TYPE_CAMERA2}.
378      */
379     @Override
getImplementationType()380     public @NonNull String getImplementationType() {
381         final int hardwareLevel = getSupportedHardwareLevel();
382         return hardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
383                 ? IMPLEMENTATION_TYPE_CAMERA2_LEGACY : IMPLEMENTATION_TYPE_CAMERA2;
384     }
385 
386     @FloatRange(from = 0, fromInclusive = false)
387     @Override
getIntrinsicZoomRatio()388     public float getIntrinsicZoomRatio() {
389         final Integer lensFacing =
390                 mCameraCharacteristicsCompat.get(CameraCharacteristics.LENS_FACING);
391         if (lensFacing == null) {
392             return INTRINSIC_ZOOM_RATIO_UNKNOWN;
393         }
394 
395         int fovDegrees;
396         int defaultFovDegrees;
397         try {
398             fovDegrees =
399                     FovUtil.focalLengthToViewAngleDegrees(
400                             FovUtil.getDefaultFocalLength(mCameraCharacteristicsCompat),
401                             FovUtil.getSensorHorizontalLength(mCameraCharacteristicsCompat));
402             defaultFovDegrees = FovUtil.getDeviceDefaultViewAngleDegrees(mCameraManager,
403                     lensFacing);
404         } catch (Exception e) {
405             Logger.e(TAG, "The camera is unable to provide necessary information to resolve its "
406                     + "intrinsic zoom ratio with error: " + e);
407             return INTRINSIC_ZOOM_RATIO_UNKNOWN;
408         }
409 
410         return ((float) defaultFovDegrees) / fovDegrees;
411     }
412 
413     @Override
isFocusMeteringSupported(@onNull FocusMeteringAction action)414     public boolean isFocusMeteringSupported(@NonNull FocusMeteringAction action) {
415         synchronized (mLock) {
416             if (mCamera2CameraControlImpl == null) {
417                 return false;
418             }
419             return mCamera2CameraControlImpl.getFocusMeteringControl().isFocusMeteringSupported(
420                     action);
421         }
422     }
423 
424     @SuppressLint("NullAnnotationGroup")
425     @OptIn(markerClass = androidx.camera.core.ExperimentalZeroShutterLag.class)
426     @Override
isZslSupported()427     public boolean isZslSupported() {
428         return Build.VERSION.SDK_INT >= 23 && isPrivateReprocessingSupported()
429                 && (DeviceQuirks.get(ZslDisablerQuirk.class) == null);
430     }
431 
432     @Override
isPrivateReprocessingSupported()433     public boolean isPrivateReprocessingSupported() {
434         return isCapabilitySupported(mCameraCharacteristicsCompat,
435                 REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING);
436     }
437 
438     @Override
isLogicalMultiCameraSupported()439     public boolean isLogicalMultiCameraSupported() {
440         return isCapabilitySupported(mCameraCharacteristicsCompat,
441                 REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA);
442     }
443 
444     /** {@inheritDoc} */
445     @Override
getEncoderProfilesProvider()446     public @NonNull EncoderProfilesProvider getEncoderProfilesProvider() {
447         return mCamera2EncoderProfilesProvider;
448     }
449 
450     @Override
getTimebase()451     public @NonNull Timebase getTimebase() {
452         Integer timeSource = mCameraCharacteristicsCompat.get(
453                 CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE);
454         Preconditions.checkNotNull(timeSource);
455         switch (timeSource) {
456             case SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME:
457                 return Timebase.REALTIME;
458             case SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN:
459             default:
460                 return Timebase.UPTIME;
461         }
462     }
463 
464     @Override
getSupportedOutputFormats()465     public @NonNull Set<Integer> getSupportedOutputFormats() {
466         StreamConfigurationMapCompat mapCompat =
467                 mCameraCharacteristicsCompat.getStreamConfigurationMapCompat();
468         int[] formats = mapCompat.getOutputFormats();
469         if (formats == null) {
470             return new HashSet<>();
471         }
472 
473         Set<Integer> result = new HashSet<>();
474         for (int format : formats) {
475             result.add(format);
476         }
477         return result;
478     }
479 
480     @Override
getSupportedResolutions(int format)481     public @NonNull List<Size> getSupportedResolutions(int format) {
482         StreamConfigurationMapCompat mapCompat =
483                 mCameraCharacteristicsCompat.getStreamConfigurationMapCompat();
484         Size[] size = mapCompat.getOutputSizes(format);
485         return size != null ? Arrays.asList(size) : Collections.emptyList();
486     }
487 
488     @Override
getSupportedHighResolutions(int format)489     public @NonNull List<Size> getSupportedHighResolutions(int format) {
490         StreamConfigurationMapCompat mapCompat =
491                 mCameraCharacteristicsCompat.getStreamConfigurationMapCompat();
492         Size[] size = mapCompat.getHighResolutionOutputSizes(format);
493         return size != null ? Arrays.asList(size) : Collections.emptyList();
494     }
495 
496     @Override
getSupportedDynamicRanges()497     public @NonNull Set<DynamicRange> getSupportedDynamicRanges() {
498         DynamicRangesCompat dynamicRangesCompat = DynamicRangesCompat.fromCameraCharacteristics(
499                 mCameraCharacteristicsCompat);
500 
501         return dynamicRangesCompat.getSupportedDynamicRanges();
502     }
503 
504     @Override
isHighSpeedSupported()505     public boolean isHighSpeedSupported() {
506         return Build.VERSION.SDK_INT >= 23 && isCapabilitySupported(mCameraCharacteristicsCompat,
507                 REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO);
508     }
509 
510     @Override
getSupportedHighSpeedFrameRateRanges()511     public @NonNull Set<Range<Integer>> getSupportedHighSpeedFrameRateRanges() {
512         Range<Integer>[] ranges = mCameraCharacteristicsCompat.getStreamConfigurationMapCompat()
513                 .getHighSpeedVideoFpsRanges();
514         return ranges != null ? new HashSet<>(Arrays.asList(ranges)) : Collections.emptySet();
515     }
516 
517     @Override
getSupportedHighSpeedFrameRateRangesFor( @onNull Size size)518     public @NonNull Set<Range<Integer>> getSupportedHighSpeedFrameRateRangesFor(
519             @NonNull Size size) {
520         Range<Integer>[] ranges = null;
521         try {
522             ranges = mCameraCharacteristicsCompat.getStreamConfigurationMapCompat()
523                     .getHighSpeedVideoFpsRangesFor(size);
524         } catch (IllegalArgumentException e) {
525             Logger.w(TAG, "Can't get high speed frame rate ranges for " + size, e);
526         }
527         return ranges != null ? new HashSet<>(Arrays.asList(ranges)) : Collections.emptySet();
528     }
529 
530     @Override
getSupportedHighSpeedResolutions()531     public @NonNull List<Size> getSupportedHighSpeedResolutions() {
532         Size[] sizes = mCameraCharacteristicsCompat.getStreamConfigurationMapCompat()
533                 .getHighSpeedVideoSizes();
534         return sizes != null ? Arrays.asList(sizes) : Collections.emptyList();
535     }
536 
537     @Override
getSupportedHighSpeedResolutionsFor( @onNull Range<Integer> fpsRange)538     public @NonNull List<Size> getSupportedHighSpeedResolutionsFor(
539             @NonNull Range<Integer> fpsRange) {
540         Size[] sizes = null;
541         try {
542             sizes = mCameraCharacteristicsCompat.getStreamConfigurationMapCompat()
543                     .getHighSpeedVideoSizesFor(fpsRange);
544         } catch (IllegalArgumentException e) {
545             Logger.w(TAG, "Can't get high speed resolutions for " + fpsRange, e);
546         }
547         return sizes != null ? Arrays.asList(sizes) : Collections.emptyList();
548     }
549 
550     @Override
querySupportedDynamicRanges( @onNull Set<DynamicRange> candidateDynamicRanges)551     public @NonNull Set<DynamicRange> querySupportedDynamicRanges(
552             @NonNull Set<DynamicRange> candidateDynamicRanges) {
553         return DynamicRanges.findAllPossibleMatches(candidateDynamicRanges,
554                 getSupportedDynamicRanges());
555     }
556 
557     @Override
addSessionCaptureCallback(@onNull Executor executor, @NonNull CameraCaptureCallback callback)558     public void addSessionCaptureCallback(@NonNull Executor executor,
559             @NonNull CameraCaptureCallback callback) {
560         synchronized (mLock) {
561             if (mCamera2CameraControlImpl == null) {
562                 if (mCameraCaptureCallbacks == null) {
563                     mCameraCaptureCallbacks = new ArrayList<>();
564                 }
565                 mCameraCaptureCallbacks.add(new Pair<>(callback, executor));
566                 return;
567             }
568 
569             mCamera2CameraControlImpl.addSessionCameraCaptureCallback(executor, callback);
570         }
571     }
572 
573     @Override
removeSessionCaptureCallback(@onNull CameraCaptureCallback callback)574     public void removeSessionCaptureCallback(@NonNull CameraCaptureCallback callback) {
575         synchronized (mLock) {
576             if (mCamera2CameraControlImpl == null) {
577                 if (mCameraCaptureCallbacks == null) {
578                     return;
579                 }
580                 Iterator<Pair<CameraCaptureCallback, Executor>> it =
581                         mCameraCaptureCallbacks.iterator();
582                 while (it.hasNext()) {
583                     Pair<CameraCaptureCallback, Executor> pair = it.next();
584                     if (pair.first == callback) {
585                         it.remove();
586                     }
587                 }
588                 return;
589             }
590             mCamera2CameraControlImpl.removeSessionCameraCaptureCallback(callback);
591         }
592     }
593 
594     /** {@inheritDoc} */
595     @Override
getCameraQuirks()596     public @NonNull Quirks getCameraQuirks() {
597         return mCameraQuirks;
598     }
599 
600     @Override
getSupportedFrameRateRanges()601     public @NonNull Set<Range<Integer>> getSupportedFrameRateRanges() {
602         Range<Integer>[] availableTargetFpsRanges =
603                 mCameraCharacteristicsCompat.get(
604                         CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
605         if (availableTargetFpsRanges != null) {
606             return new HashSet<>(Arrays.asList(availableTargetFpsRanges));
607         } else {
608             return Collections.emptySet();
609         }
610     }
611 
612     @Override
isVideoStabilizationSupported()613     public boolean isVideoStabilizationSupported() {
614         int[] availableVideoStabilizationModes =
615                 mCameraCharacteristicsCompat.get(
616                         CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES);
617         if (availableVideoStabilizationModes != null) {
618             for (int mode : availableVideoStabilizationModes) {
619                 if (mode == CONTROL_VIDEO_STABILIZATION_MODE_ON) {
620                     return true;
621                 }
622             }
623         }
624         return false;
625     }
626 
627     @Override
isPreviewStabilizationSupported()628     public boolean isPreviewStabilizationSupported() {
629         int[] availableVideoStabilizationModes =
630                 mCameraCharacteristicsCompat.get(
631                         CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES);
632         if (availableVideoStabilizationModes != null) {
633             for (int mode : availableVideoStabilizationModes) {
634                 if (mode == CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION) {
635                     return true;
636                 }
637             }
638         }
639         return false;
640     }
641 
642     /**
643      * Gets the implementation of {@link Camera2CameraInfo}.
644      */
getCamera2CameraInfo()645     public @NonNull Camera2CameraInfo getCamera2CameraInfo() {
646         return mCamera2CameraInfo;
647     }
648 
649     @Override
getCameraCharacteristics()650     public @NonNull Object getCameraCharacteristics() {
651         return mCameraCharacteristicsCompat.toCameraCharacteristics();
652     }
653 
654     @Override
getPhysicalCameraCharacteristics(@onNull String physicalCameraId)655     public @Nullable Object getPhysicalCameraCharacteristics(@NonNull String physicalCameraId) {
656         try {
657             if (!mCameraCharacteristicsCompat.getPhysicalCameraIds().contains(physicalCameraId)) {
658                 return null;
659             }
660             return mCameraManager.getCameraCharacteristicsCompat(physicalCameraId)
661                     .toCameraCharacteristics();
662         } catch (CameraAccessExceptionCompat e) {
663             Logger.e(TAG,
664                     "Failed to get CameraCharacteristics for cameraId " + physicalCameraId,
665                     e);
666         }
667         return null;
668     }
669 
670     /**
671      * Returns a map consisting of the camera ids and the {@link CameraCharacteristics}s.
672      *
673      * <p>For every camera, the map contains at least the CameraCharacteristics for the camera id.
674      * If the camera is logical camera, it will also contain associated physical camera ids and
675      * their CameraCharacteristics.
676      *
677      */
getCameraCharacteristicsMap()678     public @NonNull Map<String, CameraCharacteristics> getCameraCharacteristicsMap() {
679         LinkedHashMap<String, CameraCharacteristics> map = new LinkedHashMap<>();
680 
681         map.put(mCameraId, mCameraCharacteristicsCompat.toCameraCharacteristics());
682 
683         for (String physicalCameraId : mCameraCharacteristicsCompat.getPhysicalCameraIds()) {
684             if (Objects.equals(physicalCameraId, mCameraId)) {
685                 continue;
686             }
687             try {
688                 map.put(physicalCameraId,
689                         mCameraManager.getCameraCharacteristicsCompat(physicalCameraId)
690                                 .toCameraCharacteristics());
691             } catch (CameraAccessExceptionCompat e) {
692                 Logger.e(TAG,
693                         "Failed to get CameraCharacteristics for cameraId " + physicalCameraId, e);
694             }
695         }
696         return map;
697     }
698 
699     @Override
getPhysicalCameraInfos()700     public @NonNull Set<CameraInfo> getPhysicalCameraInfos() {
701         if (mPhysicalCameraInfos == null) {
702             mPhysicalCameraInfos = new HashSet<>();
703             for (String physicalCameraId : mCameraCharacteristicsCompat.getPhysicalCameraIds()) {
704                 try {
705                     CameraInfo physicalCameraInfo = new Camera2PhysicalCameraInfoImpl(
706                             physicalCameraId,
707                             mCameraManager);
708                     mPhysicalCameraInfos.add(physicalCameraInfo);
709                 } catch (CameraAccessExceptionCompat e) {
710                     Logger.e(TAG,
711                             "Failed to get CameraCharacteristics for cameraId " + physicalCameraId,
712                             e);
713                     return Collections.emptySet();
714                 }
715             }
716         }
717 
718         return mPhysicalCameraInfos;
719     }
720 
721     @Override
722     @IntRange(from = 0)
getMaxTorchStrengthLevel()723     public int getMaxTorchStrengthLevel() {
724         return isTorchStrengthSupported() ? mCameraCharacteristicsCompat.getMaxTorchStrengthLevel()
725                 : TORCH_STRENGTH_LEVEL_UNSUPPORTED;
726     }
727 
728     @Override
getTorchStrengthLevel()729     public @NonNull LiveData<Integer> getTorchStrengthLevel() {
730         synchronized (mLock) {
731             if (mCamera2CameraControlImpl == null) {
732                 if (mRedirectTorchStrengthLiveData == null) {
733                     mRedirectTorchStrengthLiveData = new RedirectableLiveData<>(
734                             isTorchStrengthSupported()
735                                     ? mCameraCharacteristicsCompat.getDefaultTorchStrengthLevel()
736                                     : TORCH_STRENGTH_LEVEL_UNSUPPORTED);
737                 }
738                 return mRedirectTorchStrengthLiveData;
739             }
740 
741             if (mRedirectTorchStrengthLiveData != null) {
742                 return mRedirectTorchStrengthLiveData;
743             }
744 
745             return mCamera2CameraControlImpl.getTorchControl().getTorchStrengthLevel();
746         }
747     }
748 
749     @Override
isTorchStrengthSupported()750     public boolean isTorchStrengthSupported() {
751         return mCameraCharacteristicsCompat.isTorchStrengthLevelSupported();
752     }
753 }
754