1 /*
2  * Copyright (C) 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.testing.fakes;
18 
19 import static androidx.camera.core.DynamicRange.SDR;
20 
21 import android.content.Context;
22 import android.hardware.camera2.CameraAccessException;
23 import android.hardware.camera2.CameraManager;
24 import android.util.Range;
25 import android.util.Rational;
26 import android.util.Size;
27 import android.view.Surface;
28 
29 import androidx.annotation.FloatRange;
30 import androidx.annotation.RestrictTo;
31 import androidx.camera.core.CameraSelector;
32 import androidx.camera.core.CameraState;
33 import androidx.camera.core.DynamicRange;
34 import androidx.camera.core.ExposureState;
35 import androidx.camera.core.FocusMeteringAction;
36 import androidx.camera.core.TorchState;
37 import androidx.camera.core.ZoomState;
38 import androidx.camera.core.impl.CameraCaptureCallback;
39 import androidx.camera.core.impl.CameraInfoInternal;
40 import androidx.camera.core.impl.DynamicRanges;
41 import androidx.camera.core.impl.EncoderProfilesProvider;
42 import androidx.camera.core.impl.ImageOutputConfig.RotationValue;
43 import androidx.camera.core.impl.Quirk;
44 import androidx.camera.core.impl.Quirks;
45 import androidx.camera.core.impl.Timebase;
46 import androidx.camera.core.impl.utils.CameraOrientationUtil;
47 import androidx.camera.core.internal.ImmutableZoomState;
48 import androidx.core.util.Preconditions;
49 import androidx.lifecycle.LiveData;
50 import androidx.lifecycle.MutableLiveData;
51 import androidx.test.core.app.ApplicationProvider;
52 
53 import org.jspecify.annotations.NonNull;
54 import org.jspecify.annotations.Nullable;
55 
56 import java.util.ArrayList;
57 import java.util.Arrays;
58 import java.util.Collections;
59 import java.util.HashMap;
60 import java.util.HashSet;
61 import java.util.List;
62 import java.util.Map;
63 import java.util.Set;
64 import java.util.concurrent.Executor;
65 
66 /**
67  * Fake implementation for retrieving camera information of a fake camera.
68  *
69  * <p>This camera info can be constructed with fake values.
70  */
71 public final class FakeCameraInfoInternal implements CameraInfoInternal {
72     private static final Set<Range<Integer>> FAKE_FPS_RANGES = Collections.unmodifiableSet(
73             new HashSet<>(Arrays.asList(
74                     new Range<>(12, 30),
75                     new Range<>(30, 30),
76                     new Range<>(60, 60))
77             )
78     );
79     private static final Set<DynamicRange> DEFAULT_DYNAMIC_RANGES = Collections.singleton(SDR);
80     private final String mCameraId;
81     private final int mSensorRotation;
82     @CameraSelector.LensFacing
83     private final int mLensFacing;
84     private final MutableLiveData<Integer> mTorchState = new MutableLiveData<>(TorchState.OFF);
85     private final MutableLiveData<ZoomState> mZoomLiveData;
86     private final Map<Integer, List<Size>> mSupportedResolutionMap = new HashMap<>();
87     private final Map<Range<Integer>, List<Size>> mSupportedHighSpeedFpsToSizeMap = new HashMap<>();
88     private final Map<Integer, List<Size>> mSupportedHighResolutionMap = new HashMap<>();
89     private MutableLiveData<CameraState> mCameraStateMutableLiveData;
90 
91     private final Set<DynamicRange> mSupportedDynamicRanges = new HashSet<>(DEFAULT_DYNAMIC_RANGES);
92     private String mImplementationType = IMPLEMENTATION_TYPE_FAKE;
93 
94     // Leave uninitialized to support camera-core:1.0.0 dependencies.
95     // Can be initialized during class init once there are no more pinned dependencies on
96     // camera-core:1.0.0
97     private EncoderProfilesProvider mEncoderProfilesProvider;
98 
99     private boolean mIsPrivateReprocessingSupported = false;
100     private float mIntrinsicZoomRatio = 1.0F;
101 
102     private boolean mIsFocusMeteringSupported = false;
103     private boolean mIsHighSpeedSupported = false;
104 
105     private ExposureState mExposureState = new FakeExposureState();
106     private final @NonNull List<Quirk> mCameraQuirks = new ArrayList<>();
107 
108     private Timebase mTimebase = Timebase.UPTIME;
109 
110     private @Nullable CameraManager mCameraManager;
111 
FakeCameraInfoInternal()112     public FakeCameraInfoInternal() {
113         this(/*sensorRotation=*/ 0, /*lensFacing=*/ CameraSelector.LENS_FACING_BACK);
114     }
115 
116     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
FakeCameraInfoInternal(@onNull String cameraId, @NonNull Context context)117     public FakeCameraInfoInternal(@NonNull String cameraId,
118             @NonNull Context context) {
119         this(cameraId, 0, CameraSelector.LENS_FACING_BACK, context);
120     }
121 
FakeCameraInfoInternal(@onNull String cameraId)122     public FakeCameraInfoInternal(@NonNull String cameraId) {
123         this(cameraId, 0, CameraSelector.LENS_FACING_BACK,
124                 ApplicationProvider.getApplicationContext());
125     }
126 
FakeCameraInfoInternal(@onNull String cameraId, @CameraSelector.LensFacing int lensFacing)127     public FakeCameraInfoInternal(@NonNull String cameraId,
128             @CameraSelector.LensFacing int lensFacing) {
129         this(cameraId, 0, lensFacing,
130                 ApplicationProvider.getApplicationContext());
131     }
132 
FakeCameraInfoInternal(int sensorRotation, @CameraSelector.LensFacing int lensFacing)133     public FakeCameraInfoInternal(int sensorRotation, @CameraSelector.LensFacing int lensFacing) {
134         this("0", sensorRotation, lensFacing,
135                 ApplicationProvider.getApplicationContext());
136     }
137 
FakeCameraInfoInternal(@onNull String cameraId, int sensorRotation, @CameraSelector.LensFacing int lensFacing)138     public FakeCameraInfoInternal(@NonNull String cameraId, int sensorRotation,
139             @CameraSelector.LensFacing int lensFacing) {
140         this(cameraId, sensorRotation, lensFacing,
141                 ApplicationProvider.getApplicationContext());
142     }
143 
144     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
FakeCameraInfoInternal(@onNull String cameraId, int sensorRotation, @CameraSelector.LensFacing int lensFacing, @NonNull Context context)145     public FakeCameraInfoInternal(@NonNull String cameraId, int sensorRotation,
146             @CameraSelector.LensFacing int lensFacing,
147             @NonNull Context context) {
148         mCameraId = cameraId;
149         mSensorRotation = sensorRotation;
150         mLensFacing = lensFacing;
151         mZoomLiveData = new MutableLiveData<>(ImmutableZoomState.create(1.0f, 4.0f, 1.0f, 0.0f));
152         mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
153     }
154 
155     /**
156      * Sets the zoom parameter.
157      */
setZoom(float zoomRatio, float minZoomRatio, float maxZoomRatio, float linearZoom)158     public void setZoom(float zoomRatio, float minZoomRatio, float maxZoomRatio, float linearZoom) {
159         mZoomLiveData.postValue(ImmutableZoomState.create(
160                 zoomRatio, maxZoomRatio, minZoomRatio, linearZoom
161         ));
162     }
163 
164     /**
165      * Sets the exposure compensation parameters.
166      */
setExposureState(int index, @NonNull Range<Integer> range, @NonNull Rational step, boolean isSupported)167     public void setExposureState(int index, @NonNull Range<Integer> range,
168             @NonNull Rational step, boolean isSupported) {
169         mExposureState = new FakeExposureState(index, range, step, isSupported);
170     }
171 
172     /**
173      * Sets the torch state.
174      */
setTorch(int torchState)175     public void setTorch(int torchState) {
176         mTorchState.postValue(torchState);
177     }
178 
179     /**
180      * Sets the return value for {@link #isFocusMeteringSupported(FocusMeteringAction)}.
181      */
setIsFocusMeteringSupported(boolean supported)182     public void setIsFocusMeteringSupported(boolean supported) {
183         mIsFocusMeteringSupported = supported;
184     }
185 
186     @Override
getLensFacing()187     public int getLensFacing() {
188         return mLensFacing;
189     }
190 
191     @Override
getCameraId()192     public @NonNull String getCameraId() {
193         return mCameraId;
194     }
195 
196     @Override
getSensorRotationDegrees(@otationValue int relativeRotation)197     public int getSensorRotationDegrees(@RotationValue int relativeRotation) {
198         int relativeRotationDegrees =
199                 CameraOrientationUtil.surfaceRotationToDegrees(relativeRotation);
200         // Currently this assumes that a back-facing camera is always opposite to the screen.
201         // This may not be the case for all devices, so in the future we may need to handle that
202         // scenario.
203         Integer lensFacing = getLensFacing();
204         boolean isOppositeFacingScreen =
205                 lensFacing != null && (CameraSelector.LENS_FACING_BACK == getLensFacing());
206         return CameraOrientationUtil.getRelativeImageRotation(
207                 relativeRotationDegrees,
208                 mSensorRotation,
209                 isOppositeFacingScreen);
210     }
211 
212     @Override
getSensorRotationDegrees()213     public int getSensorRotationDegrees() {
214         return getSensorRotationDegrees(Surface.ROTATION_0);
215     }
216 
217     @Override
hasFlashUnit()218     public boolean hasFlashUnit() {
219         return true;
220     }
221 
222     @Override
getTorchState()223     public @NonNull LiveData<Integer> getTorchState() {
224         return mTorchState;
225     }
226 
227     @Override
getZoomState()228     public @NonNull LiveData<ZoomState> getZoomState() {
229         return mZoomLiveData;
230     }
231 
232     @Override
getExposureState()233     public @NonNull ExposureState getExposureState() {
234         return mExposureState;
235     }
236 
getCameraStateMutableLiveData()237     private MutableLiveData<CameraState> getCameraStateMutableLiveData() {
238         if (mCameraStateMutableLiveData == null) {
239             mCameraStateMutableLiveData = new MutableLiveData<>(
240                     CameraState.create(CameraState.Type.CLOSED));
241         }
242         return mCameraStateMutableLiveData;
243     }
244 
245     @Override
getCameraState()246     public @NonNull LiveData<CameraState> getCameraState() {
247         return getCameraStateMutableLiveData();
248     }
249 
250     @Override
getImplementationType()251     public @NonNull String getImplementationType() {
252         return mImplementationType;
253     }
254 
255     @Override
getEncoderProfilesProvider()256     public @NonNull EncoderProfilesProvider getEncoderProfilesProvider() {
257         return mEncoderProfilesProvider == null ? EncoderProfilesProvider.EMPTY :
258                 mEncoderProfilesProvider;
259     }
260 
261     @Override
getTimebase()262     public @NonNull Timebase getTimebase() {
263         return mTimebase;
264     }
265 
266     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
267     @Override
getSupportedOutputFormats()268     public @NonNull Set<Integer> getSupportedOutputFormats() {
269         return mSupportedResolutionMap.keySet();
270     }
271 
272     @Override
getSupportedResolutions(int format)273     public @NonNull List<Size> getSupportedResolutions(int format) {
274         List<Size> resolutions = mSupportedResolutionMap.get(format);
275         return resolutions != null ? resolutions : Collections.emptyList();
276     }
277 
278     @Override
getSupportedHighResolutions(int format)279     public @NonNull List<Size> getSupportedHighResolutions(int format) {
280         List<Size> resolutions = mSupportedHighResolutionMap.get(format);
281         return resolutions != null ? resolutions : Collections.emptyList();
282     }
283 
284     @Override
getSupportedDynamicRanges()285     public @NonNull Set<DynamicRange> getSupportedDynamicRanges() {
286         return mSupportedDynamicRanges;
287     }
288 
289     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
290     @Override
isHighSpeedSupported()291     public boolean isHighSpeedSupported() {
292         return mIsHighSpeedSupported;
293     }
294 
295     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
296     @NonNull
297     @Override
getSupportedHighSpeedFrameRateRanges()298     public Set<Range<Integer>> getSupportedHighSpeedFrameRateRanges() {
299         return mSupportedHighSpeedFpsToSizeMap.keySet();
300     }
301 
302     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
303     @NonNull
304     @Override
getSupportedHighSpeedFrameRateRangesFor(@onNull Size size)305     public Set<Range<Integer>> getSupportedHighSpeedFrameRateRangesFor(@NonNull Size size) {
306         Set<Range<Integer>> ranges = new HashSet<>();
307         for (Map.Entry<Range<Integer>, List<Size>> entry :
308                 mSupportedHighSpeedFpsToSizeMap.entrySet()) {
309             if (entry.getValue().contains(size)) {
310                 ranges.add(entry.getKey());
311             }
312         }
313         return ranges;
314     }
315 
316     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
317     @NonNull
318     @Override
getSupportedHighSpeedResolutions()319     public List<Size> getSupportedHighSpeedResolutions() {
320         Set<Size> resolutions = new HashSet<>();
321         for (List<Size> sizes : mSupportedHighSpeedFpsToSizeMap.values()) {
322             resolutions.addAll(sizes);
323         }
324         return new ArrayList<>(resolutions);
325     }
326 
327     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
328     @NonNull
329     @Override
getSupportedHighSpeedResolutionsFor(@onNull Range<Integer> fpsRange)330     public List<Size> getSupportedHighSpeedResolutionsFor(@NonNull Range<Integer> fpsRange) {
331         List<Size> resolutions = mSupportedHighSpeedFpsToSizeMap.get(fpsRange);
332         return resolutions != null ? resolutions : Collections.emptyList();
333     }
334 
335     /**
336      * Returns the supported dynamic ranges of this camera from a set of candidate dynamic ranges.
337      *
338      * <p>The dynamic ranges which represent what the camera supports will come from the dynamic
339      * ranges set on {@link #setSupportedDynamicRanges(Set)}, or will consist of {@code {SDR}} if
340      * {@code setSupportedDynamicRanges(Set)} has not been called. In order to stay compliant
341      * with the API contract of
342      * {@link androidx.camera.core.CameraInfo#querySupportedDynamicRanges(Set)}, it is
343      * required that the {@link Set} provided to {@code setSupportedDynamicRanges(Set)} should
344      * always contain {@link DynamicRange#SDR} and should never contain under-specified dynamic
345      * ranges, such as {@link DynamicRange#UNSPECIFIED} and
346      * {@link DynamicRange#HDR_UNSPECIFIED_10_BIT}.
347      *
348      * @see androidx.camera.core.CameraInfo#querySupportedDynamicRanges(Set)
349      */
350     @Override
querySupportedDynamicRanges( @onNull Set<DynamicRange> candidateDynamicRanges)351     public @NonNull Set<DynamicRange> querySupportedDynamicRanges(
352             @NonNull Set<DynamicRange> candidateDynamicRanges) {
353         return DynamicRanges.findAllPossibleMatches(
354                 candidateDynamicRanges, getSupportedDynamicRanges());
355     }
356 
357     @Override
addSessionCaptureCallback(@onNull Executor executor, @NonNull CameraCaptureCallback callback)358     public void addSessionCaptureCallback(@NonNull Executor executor,
359             @NonNull CameraCaptureCallback callback) {
360         throw new UnsupportedOperationException("Not Implemented");
361     }
362 
363     @Override
removeSessionCaptureCallback(@onNull CameraCaptureCallback callback)364     public void removeSessionCaptureCallback(@NonNull CameraCaptureCallback callback) {
365         throw new UnsupportedOperationException("Not Implemented");
366     }
367 
368     @Override
getCameraQuirks()369     public @NonNull Quirks getCameraQuirks() {
370         return new Quirks(mCameraQuirks);
371     }
372 
373     @Override
getSupportedFrameRateRanges()374     public @NonNull Set<Range<Integer>> getSupportedFrameRateRanges() {
375         return FAKE_FPS_RANGES;
376     }
377 
378     @Override
isFocusMeteringSupported(@onNull FocusMeteringAction action)379     public boolean isFocusMeteringSupported(@NonNull FocusMeteringAction action) {
380         return mIsFocusMeteringSupported;
381     }
382 
383     @androidx.camera.core.ExperimentalZeroShutterLag
384     @Override
isZslSupported()385     public boolean isZslSupported() {
386         return false;
387     }
388 
389     @Override
isPrivateReprocessingSupported()390     public boolean isPrivateReprocessingSupported() {
391         return mIsPrivateReprocessingSupported;
392     }
393 
394     @FloatRange(from = 0, fromInclusive = false)
395     @Override
getIntrinsicZoomRatio()396     public float getIntrinsicZoomRatio() {
397         return mIntrinsicZoomRatio;
398     }
399 
400     @Override
isPreviewStabilizationSupported()401     public boolean isPreviewStabilizationSupported() {
402         return false;
403     }
404 
405     @Override
isVideoStabilizationSupported()406     public boolean isVideoStabilizationSupported() {
407         return false;
408     }
409 
410     /** Adds a quirk to the list of this camera's quirks. */
411     @SuppressWarnings("unused")
addCameraQuirk(final @NonNull Quirk quirk)412     public void addCameraQuirk(final @NonNull Quirk quirk) {
413         mCameraQuirks.add(quirk);
414     }
415 
416     /**
417      * Updates the {@link CameraState} value to the {@code LiveData} provided by
418      * {@link #getCameraState()}.
419      *
420      * @param cameraState the camera state value to set.
421      */
422     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
updateCameraState(@onNull CameraState cameraState)423     public void updateCameraState(@NonNull CameraState cameraState) {
424         getCameraStateMutableLiveData().postValue(cameraState);
425     }
426 
427     /**
428      * Set the implementation type for testing
429      */
setImplementationType(@mplementationType @onNull String implementationType)430     public void setImplementationType(@ImplementationType @NonNull String implementationType) {
431         mImplementationType = implementationType;
432     }
433 
434     /** Set the EncoderProfilesProvider for testing */
setEncoderProfilesProvider( @onNull EncoderProfilesProvider encoderProfilesProvider)435     public void setEncoderProfilesProvider(
436             @NonNull EncoderProfilesProvider encoderProfilesProvider) {
437         mEncoderProfilesProvider = Preconditions.checkNotNull(encoderProfilesProvider);
438     }
439 
440     /** Set the timebase for testing */
setTimebase(@onNull Timebase timebase)441     public void setTimebase(@NonNull Timebase timebase) {
442         mTimebase = timebase;
443     }
444 
445     /** Set the supported resolutions for testing */
setSupportedResolutions(int format, @NonNull List<Size> resolutions)446     public void setSupportedResolutions(int format, @NonNull List<Size> resolutions) {
447         mSupportedResolutionMap.put(format, resolutions);
448     }
449 
450     /** Set the supported high resolutions for testing */
setSupportedHighResolutions(int format, @NonNull List<Size> resolutions)451     public void setSupportedHighResolutions(int format, @NonNull List<Size> resolutions) {
452         mSupportedHighResolutionMap.put(format, resolutions);
453     }
454 
455     /** Sets the return value for {@link #isHighSpeedSupported()}}. */
456     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
setHighSpeedSupported(boolean supported)457     public void setHighSpeedSupported(boolean supported) {
458         mIsHighSpeedSupported = supported;
459     }
460 
461     /** Set the supported high speed resolutions for testing */
462     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
setSupportedHighSpeedResolutions(@onNull Range<Integer> fps, @NonNull List<Size> resolutions)463     public void setSupportedHighSpeedResolutions(@NonNull Range<Integer> fps,
464             @NonNull List<Size> resolutions) {
465         mSupportedHighSpeedFpsToSizeMap.put(fps, resolutions);
466     }
467 
468     /** Set the isPrivateReprocessingSupported flag for testing */
setPrivateReprocessingSupported(boolean supported)469     public void setPrivateReprocessingSupported(boolean supported) {
470         mIsPrivateReprocessingSupported = supported;
471     }
472 
473     /** Adds a available view angle for testing. */
setIntrinsicZoomRatio(float zoomRatio)474     public void setIntrinsicZoomRatio(float zoomRatio) {
475         mIntrinsicZoomRatio = zoomRatio;
476     }
477 
478     /** Set the supported dynamic ranges for testing */
setSupportedDynamicRanges(@onNull Set<DynamicRange> dynamicRanges)479     public void setSupportedDynamicRanges(@NonNull Set<DynamicRange> dynamicRanges) {
480         mSupportedDynamicRanges.clear();
481         mSupportedDynamicRanges.addAll(dynamicRanges);
482     }
483 
484     @Override
485     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
getCameraCharacteristics()486     public @NonNull Object getCameraCharacteristics() {
487         try {
488             return mCameraManager.getCameraCharacteristics(mCameraId);
489         } catch (CameraAccessException e) {
490             throw new IllegalStateException("can't get CameraCharacteristics", e);
491         }
492     }
493 
494     @Override
495     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
getPhysicalCameraCharacteristics(@onNull String physicalCameraId)496     public @Nullable Object getPhysicalCameraCharacteristics(@NonNull String physicalCameraId) {
497         try {
498             return mCameraManager.getCameraCharacteristics(physicalCameraId);
499         } catch (CameraAccessException e) {
500             throw new IllegalStateException("can't get CameraCharacteristics", e);
501         }
502     }
503 
504     static final class FakeExposureState implements ExposureState {
505         private int mIndex = 0;
506         private Range<Integer> mRange = new Range<>(0, 0);
507         private Rational mStep = Rational.ZERO;
508         private boolean mIsSupported = true;
509 
FakeExposureState()510         FakeExposureState() {
511         }
FakeExposureState(int index, Range<Integer> range, Rational step, boolean isSupported)512         FakeExposureState(int index, Range<Integer> range,
513                 Rational step, boolean isSupported) {
514             mIndex = index;
515             mRange = range;
516             mStep = step;
517             mIsSupported = isSupported;
518         }
519 
520         @Override
getExposureCompensationIndex()521         public int getExposureCompensationIndex() {
522             return mIndex;
523         }
524 
525         @Override
getExposureCompensationRange()526         public @NonNull Range<Integer> getExposureCompensationRange() {
527             return mRange;
528         }
529 
530         @Override
getExposureCompensationStep()531         public @NonNull Rational getExposureCompensationStep() {
532             return mStep;
533         }
534 
535         @Override
isExposureCompensationSupported()536         public boolean isExposureCompensationSupported() {
537             return mIsSupported;
538         }
539     }
540 }
541