1 /*
2  * Copyright 2023 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.impl.fakes;
18 
19 import static android.graphics.ImageFormat.JPEG;
20 import static android.graphics.ImageFormat.YUV_420_888;
21 
22 import static androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE;
23 
24 import static com.google.common.primitives.Ints.asList;
25 
26 import android.util.Pair;
27 import android.util.Size;
28 
29 import androidx.camera.core.impl.AttachedSurfaceInfo;
30 import androidx.camera.core.impl.CameraDeviceSurfaceManager;
31 import androidx.camera.core.impl.CameraMode;
32 import androidx.camera.core.impl.ImageAnalysisConfig;
33 import androidx.camera.core.impl.ImageCaptureConfig;
34 import androidx.camera.core.impl.PreviewConfig;
35 import androidx.camera.core.impl.StreamSpec;
36 import androidx.camera.core.impl.SurfaceConfig;
37 import androidx.camera.core.impl.UseCaseConfig;
38 import androidx.camera.core.impl.UseCaseConfigFactory;
39 import androidx.camera.core.streamsharing.StreamSharingConfig;
40 import androidx.camera.video.impl.VideoCaptureConfig;
41 
42 import org.jspecify.annotations.NonNull;
43 import org.jspecify.annotations.Nullable;
44 
45 import java.util.ArrayList;
46 import java.util.HashMap;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Set;
51 
52 /** A CameraDeviceSurfaceManager which has no supported SurfaceConfigs. */
53 public final class FakeCameraDeviceSurfaceManager implements CameraDeviceSurfaceManager {
54 
55     public static final Size MAX_OUTPUT_SIZE = new Size(4032, 3024); // 12.2 MP
56 
57     private final Map<String, Map<Class<? extends UseCaseConfig<?>>, StreamSpec>>
58             mDefinedStreamSpecs = new HashMap<>();
59 
60     private Set<List<Integer>> mValidSurfaceCombos = createDefaultValidSurfaceCombos();
61 
62     /**
63      * Sets the given suggested stream specs for the specified camera Id and use case type.
64      */
setSuggestedStreamSpec(@onNull String cameraId, @NonNull Class<? extends UseCaseConfig<?>> type, @NonNull StreamSpec streamSpec)65     public void setSuggestedStreamSpec(@NonNull String cameraId,
66             @NonNull Class<? extends UseCaseConfig<?>> type,
67             @NonNull StreamSpec streamSpec) {
68         Map<Class<? extends UseCaseConfig<?>>, StreamSpec> useCaseConfigTypeToStreamSpecMap =
69                 mDefinedStreamSpecs.get(cameraId);
70         if (useCaseConfigTypeToStreamSpecMap == null) {
71             useCaseConfigTypeToStreamSpecMap = new HashMap<>();
72             mDefinedStreamSpecs.put(cameraId, useCaseConfigTypeToStreamSpecMap);
73         }
74 
75         useCaseConfigTypeToStreamSpecMap.put(type, streamSpec);
76     }
77 
78     @Override
transformSurfaceConfig( @ameraMode.Mode int cameraMode, @NonNull String cameraId, int imageFormat, @NonNull Size size)79     public @Nullable SurfaceConfig transformSurfaceConfig(
80             @CameraMode.Mode int cameraMode,
81             @NonNull String cameraId,
82             int imageFormat,
83             @NonNull Size size) {
84 
85         //returns a placeholder SurfaceConfig
86         return SurfaceConfig.create(SurfaceConfig.ConfigType.PRIV,
87                 SurfaceConfig.ConfigSize.PREVIEW);
88     }
89 
90     @Override
91     public @NonNull Pair<Map<UseCaseConfig<?>, StreamSpec>, Map<AttachedSurfaceInfo, StreamSpec>>
getSuggestedStreamSpecs( @ameraMode.Mode int cameraMode, @NonNull String cameraId, @NonNull List<AttachedSurfaceInfo> existingSurfaces, @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap, boolean isPreviewStabilizationOn, boolean hasVideoCapture)92             getSuggestedStreamSpecs(
93             @CameraMode.Mode int cameraMode,
94             @NonNull String cameraId,
95             @NonNull List<AttachedSurfaceInfo> existingSurfaces,
96             @NonNull Map<UseCaseConfig<?>, List<Size>> newUseCaseConfigsSupportedSizeMap,
97             boolean isPreviewStabilizationOn,
98             boolean hasVideoCapture) {
99         List<UseCaseConfig<?>> newUseCaseConfigs =
100                 new ArrayList<>(newUseCaseConfigsSupportedSizeMap.keySet());
101         checkSurfaceCombo(existingSurfaces, newUseCaseConfigs);
102 
103         // Populate the suggested stream specs for new use cases.
104         Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecs = new HashMap<>();
105         for (UseCaseConfig<?> useCaseConfig : newUseCaseConfigs) {
106             suggestedStreamSpecs.put(useCaseConfig,
107                     getStreamSpec(cameraId, useCaseConfig.getClass(), hasVideoCapture));
108         }
109 
110         // Populate the stream specs for existing use cases.
111         Map<AttachedSurfaceInfo, StreamSpec> existingStreamSpecs = new HashMap<>();
112         for (AttachedSurfaceInfo attachedSurfaceInfo : existingSurfaces) {
113             existingStreamSpecs.put(attachedSurfaceInfo, getStreamSpec(cameraId,
114                     captureTypeToUseCaseConfigType(attachedSurfaceInfo.getCaptureTypes().get(0)),
115                     hasVideoCapture));
116         }
117 
118         return new Pair<>(suggestedStreamSpecs, existingStreamSpecs);
119     }
120 
getStreamSpec(@onNull String cameraId, @NonNull Class<?> classType, boolean hasVideoCapture)121     private @NonNull StreamSpec getStreamSpec(@NonNull String cameraId, @NonNull Class<?> classType,
122             boolean hasVideoCapture) {
123         StreamSpec streamSpec = StreamSpec.builder(MAX_OUTPUT_SIZE)
124                 .setZslDisabled(hasVideoCapture)
125                 .build();
126         Map<Class<? extends UseCaseConfig<?>>, StreamSpec> definedStreamSpecs =
127                 mDefinedStreamSpecs.get(cameraId);
128         if (definedStreamSpecs != null) {
129             StreamSpec definedStreamSpec = definedStreamSpecs.get(classType);
130             if (definedStreamSpec != null) {
131                 streamSpec = definedStreamSpec;
132             }
133         }
134         return streamSpec;
135     }
136 
137     /**
138      * Returns the {@link UseCaseConfig} type from a
139      * {@link androidx.camera.core.impl.UseCaseConfigFactory.CaptureType}.
140      */
captureTypeToUseCaseConfigType( UseCaseConfigFactory.@onNull CaptureType captureType)141     private Class<?> captureTypeToUseCaseConfigType(
142             UseCaseConfigFactory.@NonNull CaptureType captureType) {
143         switch (captureType) {
144             case METERING_REPEATING:
145                 // Fall-through
146             case PREVIEW:
147                 return PreviewConfig.class;
148             case IMAGE_CAPTURE:
149                 return ImageCaptureConfig.class;
150             case IMAGE_ANALYSIS:
151                 return ImageAnalysisConfig.class;
152             case VIDEO_CAPTURE:
153                 return VideoCaptureConfig.class;
154             case STREAM_SHARING:
155                 return StreamSharingConfig.class;
156             default:
157                 throw new IllegalArgumentException("Invalid capture type.");
158         }
159     }
160 
161     /**
162      * Checks if the surface combinations is supported.
163      *
164      * <p> Throws {@link IllegalArgumentException} if not supported.
165      */
checkSurfaceCombo(List<AttachedSurfaceInfo> existingSurfaceInfos, @NonNull List<UseCaseConfig<?>> newSurfaceConfigs)166     private void checkSurfaceCombo(List<AttachedSurfaceInfo> existingSurfaceInfos,
167             @NonNull List<UseCaseConfig<?>> newSurfaceConfigs) {
168         // Combine existing Surface with new Surface
169         List<Integer> currentCombo = new ArrayList<>();
170         for (UseCaseConfig<?> useCaseConfig : newSurfaceConfigs) {
171             currentCombo.add(useCaseConfig.getInputFormat());
172         }
173         for (AttachedSurfaceInfo surfaceInfo : existingSurfaceInfos) {
174             currentCombo.add(surfaceInfo.getImageFormat());
175         }
176         // Loop through valid combinations and return early if the combo is supported.
177         for (List<Integer> validCombo : mValidSurfaceCombos) {
178             if (isComboSupported(currentCombo, validCombo)) {
179                 return;
180             }
181         }
182         // Throw IAE if none of the valid combos supports the current combo.
183         throw new IllegalArgumentException("Surface combo not supported");
184     }
185 
186     /**
187      * Checks if the app combination in covered by the given valid combination.
188      */
isComboSupported(@onNull List<Integer> appCombo, @NonNull List<Integer> validCombo)189     private boolean isComboSupported(@NonNull List<Integer> appCombo,
190             @NonNull List<Integer> validCombo) {
191         List<Integer> combo = new ArrayList<>(validCombo);
192         for (Integer format : appCombo) {
193             if (!combo.remove(format)) {
194                 return false;
195             }
196         }
197         return true;
198     }
199 
200     /**
201      * The default combination is similar to LEGACY level devices.
202      */
createDefaultValidSurfaceCombos()203     private static Set<List<Integer>> createDefaultValidSurfaceCombos() {
204         Set<List<Integer>> validCombos = new HashSet<>();
205         validCombos.add(asList(INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE, YUV_420_888, JPEG));
206         validCombos.add(asList(INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE,
207                 INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE));
208         return validCombos;
209     }
210 
setValidSurfaceCombos(@onNull Set<List<Integer>> validSurfaceCombos)211     public void setValidSurfaceCombos(@NonNull Set<List<Integer>> validSurfaceCombos) {
212         mValidSurfaceCombos = validSurfaceCombos;
213     }
214 }
215