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