Check whether stream use case is supported and invoke populateStreamUseCaseStreamSpecOption() in SupportedSurfaceCombination

Test: SupportedSurfaceCombinationTest
Bug: 266879290
Change-Id: I5406f54fa9065ab37dbe105c077b232586bf5b38
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java
index 334952fd..1213e14 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java
@@ -689,6 +689,132 @@
     }
 
     /**
+     * Returns the entire supported stream combinations for devices with Stream Use Case capability
+     */
+    @NonNull
+    public static List<SurfaceCombination> getStreamUseCaseSupportedCombinationList() {
+        List<SurfaceCombination> combinationList = new ArrayList<>();
+
+        // (PRIV, s1440p)
+        SurfaceCombination surfaceCombination1 = new SurfaceCombination();
+        surfaceCombination1.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.s1440p));
+        combinationList.add(surfaceCombination1);
+
+        // (YUV, s1440p)
+        SurfaceCombination surfaceCombination2 = new SurfaceCombination();
+        surfaceCombination2.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.s1440p));
+        combinationList.add(surfaceCombination2);
+
+        // (PRIV, RECORD)
+        SurfaceCombination surfaceCombination3 = new SurfaceCombination();
+        surfaceCombination3.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD));
+        combinationList.add(surfaceCombination3);
+
+        // (YUV, RECORD)
+        SurfaceCombination surfaceCombination4 = new SurfaceCombination();
+        surfaceCombination4.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD));
+        combinationList.add(surfaceCombination4);
+
+        // (JPEG, MAXIMUM)
+        SurfaceCombination surfaceCombination5 = new SurfaceCombination();
+        surfaceCombination5.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination5);
+
+        // (YUV, MAXIMUM)
+        SurfaceCombination surfaceCombination6 = new SurfaceCombination();
+        surfaceCombination6.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination6);
+
+        // (PRIV, PREVIEW) + (JPEG, MAXIMUM)
+        SurfaceCombination surfaceCombination7 = new SurfaceCombination();
+        surfaceCombination7.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination7.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination7);
+
+        // (PRIV, PREVIEW) + (YUV, MAXIMUM)
+        SurfaceCombination surfaceCombination8 = new SurfaceCombination();
+        surfaceCombination8.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination8.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination8);
+
+        // (PRIV, PREVIEW) + (PRIV, RECORD)
+        SurfaceCombination surfaceCombination9 = new SurfaceCombination();
+        surfaceCombination9.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination9.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD));
+        combinationList.add(surfaceCombination9);
+
+        // (PRIV, PREVIEW) + (YUV, RECORD)
+        SurfaceCombination surfaceCombination10 = new SurfaceCombination();
+        surfaceCombination10.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination10.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD));
+        combinationList.add(surfaceCombination10);
+
+        // (PRIV, PREVIEW) + (YUV, PREVIEW)
+        SurfaceCombination surfaceCombination11 = new SurfaceCombination();
+        surfaceCombination11.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination11.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        combinationList.add(surfaceCombination11);
+
+        // (PRIV, PREVIEW) + (PRIV, RECORD) + (JPEG, RECORD)
+        SurfaceCombination surfaceCombination12 = new SurfaceCombination();
+        surfaceCombination12.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination12.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD));
+        surfaceCombination12.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD));
+        combinationList.add(surfaceCombination12);
+
+        // (PRIV, PREVIEW) + (PRIV, RECORD) + (JPEG, RECORD)
+        SurfaceCombination surfaceCombination13 = new SurfaceCombination();
+        surfaceCombination13.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination13.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD));
+        surfaceCombination13.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD));
+        combinationList.add(surfaceCombination13);
+
+        // (PRIV, PREVIEW) + (YUV, RECORD) + (JPEG, RECORD)
+        SurfaceCombination surfaceCombination14 = new SurfaceCombination();
+        surfaceCombination14.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination14.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD));
+        surfaceCombination14.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD));
+        combinationList.add(surfaceCombination14);
+
+        // (PRIV, PREVIEW) + (YUV, PREVIEW) + (JPEG, MAXIMUM)
+        SurfaceCombination surfaceCombination15 = new SurfaceCombination();
+        surfaceCombination15.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination15.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination15.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination14);
+
+        return combinationList;
+    }
+
+    /**
      * Returns the supported stream combinations based on the hardware level and capabilities of
      * the device.
      */
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/StreamUseCaseUtil.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/StreamUseCaseUtil.java
index 1f1efeb..82bfaf2 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/StreamUseCaseUtil.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/StreamUseCaseUtil.java
@@ -212,6 +212,32 @@
     }
 
     /**
+     * Return true if the given camera characteristics support stream use case
+     */
+    @OptIn(markerClass = ExperimentalCamera2Interop.class)
+    public static boolean isStreamUseCaseSupported(
+            @NonNull CameraCharacteristicsCompat characteristicsCompat) {
+        if (Build.VERSION.SDK_INT < 33) {
+            return false;
+        }
+        long[] availableStreamUseCases = characteristicsCompat.get(
+                CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES);
+        if (availableStreamUseCases == null || availableStreamUseCases.length == 0) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Return true if the given feature settings is appropriate for stream use case usage.
+     */
+    public static boolean shouldUseStreamUseCase(@NonNull
+            SupportedSurfaceCombination.FeatureSettings featureSettings) {
+        return featureSettings.getCameraMode() == CameraMode.DEFAULT
+                && featureSettings.getRequiredMaxBitDepth() == DynamicRange.BIT_DEPTH_8_BIT;
+    }
+
+    /**
      * Populate the {@link STREAM_USE_CASE_STREAM_SPEC_OPTION} option in StreamSpecs for both
      * existing UseCases and new UseCases to be attached. This option will be written into the
      * session configurations of the UseCases. When creating a new capture session during
@@ -219,30 +245,20 @@
      * {@link android.hardware.camera2.params.OutputConfiguration#setStreamUseCase(long)}
      *
      * @param characteristicsCompat        the camera characteristics of the device
-     * @param featureSettings              the feature settings of this capture
      * @param attachedSurfaces             surface info of the already attached use cases
      * @param suggestedStreamSpecMap       the UseCaseConfig-to-StreamSpec map for new use cases
      * @param attachedSurfaceStreamSpecMap the SurfaceInfo-to-StreamSpec map for attached use cases
      *                                     whose StreamSpecs needs to be updated
+     * @return true if StreamSpec options are populated. False if not.
      */
     @OptIn(markerClass = ExperimentalCamera2Interop.class)
-    public static void populateStreamUseCaseStreamSpecOption(
+    public static boolean populateStreamUseCaseStreamSpecOptionWithInteropOverride(
             @NonNull CameraCharacteristicsCompat characteristicsCompat,
-            @NonNull SupportedSurfaceCombination.FeatureSettings featureSettings,
             @NonNull List<AttachedSurfaceInfo> attachedSurfaces,
             @NonNull Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecMap,
             @NonNull Map<AttachedSurfaceInfo, StreamSpec> attachedSurfaceStreamSpecMap) {
         if (Build.VERSION.SDK_INT < 33) {
-            return;
-        }
-        long[] availableStreamUseCases = characteristicsCompat.get(
-                CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES);
-        if (availableStreamUseCases == null || availableStreamUseCases.length == 0) {
-            return;
-        }
-        if (featureSettings.getCameraMode() != CameraMode.DEFAULT
-                || featureSettings.getRequiredMaxBitDepth() != DynamicRange.BIT_DEPTH_8_BIT) {
-            return;
+            return false;
         }
         List<UseCaseConfig<?>> newUseCaseConfigs = new ArrayList<>(suggestedStreamSpecMap.keySet());
         // All AttachedSurfaceInfo should have implementation options
@@ -254,10 +270,9 @@
             Preconditions.checkNotNull(Preconditions.checkNotNull(
                     suggestedStreamSpecMap.get(useCaseConfig)).getImplementationOptions());
         }
-        if (containsZslUseCase(attachedSurfaces, newUseCaseConfigs)) {
-            return;
-        }
         Set<Long> availableStreamUseCaseSet = new HashSet<>();
+        long[] availableStreamUseCases = characteristicsCompat.get(
+                CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES);
         for (Long availableStreamUseCase : availableStreamUseCases) {
             availableStreamUseCaseSet.add(availableStreamUseCase);
         }
@@ -297,7 +312,9 @@
                     suggestedStreamSpecMap.put(newUseCaseConfig, newStreamSpec);
                 }
             }
+            return true;
         }
+        return false;
     }
 
     /**
@@ -324,9 +341,9 @@
     /**
      * Return true if any one of the existing or new UseCases is ZSL.
      */
-    private static boolean containsZslUseCase(
-            List<AttachedSurfaceInfo> attachedSurfaces,
-            List<UseCaseConfig<?>> newUseCaseConfigs) {
+    public static boolean containsZslUseCase(
+            @NonNull List<AttachedSurfaceInfo> attachedSurfaces,
+            @NonNull List<UseCaseConfig<?>> newUseCaseConfigs) {
         for (AttachedSurfaceInfo attachedSurfaceInfo : attachedSurfaces) {
             List<UseCaseConfigFactory.CaptureType> captureTypes =
                     attachedSurfaceInfo.getCaptureTypes();
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
index b9a91cf..bb83079 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
@@ -41,6 +41,7 @@
 import androidx.annotation.DoNotInline;
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
@@ -87,6 +88,7 @@
  * devices. This structure is used to store a list of surface combinations that are guaranteed to
  * support for this camera device.
  */
+@OptIn(markerClass = ExperimentalCamera2Interop.class)
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 final class SupportedSurfaceCombination {
     private static final String TAG = "SupportedSurfaceCombination";
@@ -96,6 +98,7 @@
     private final Map<FeatureSettings, List<SurfaceCombination>>
             mFeatureSettingsToSupportedCombinationsMap = new HashMap<>();
     private final List<SurfaceCombination> mSurfaceCombinations10Bit = new ArrayList<>();
+    private final List<SurfaceCombination> mSurfaceCombinationsStreamUseCase = new ArrayList<>();
     private final String mCameraId;
     private final CamcorderProfileHelper mCamcorderProfileHelper;
     private final CameraCharacteristicsCompat mCharacteristics;
@@ -105,6 +108,7 @@
     private boolean mIsRawSupported = false;
     private boolean mIsBurstCaptureSupported = false;
     private boolean mIsConcurrentCameraModeSupported = false;
+    private boolean mIsStreamUseCaseSupported = false;
     private boolean mIsUltraHighResolutionSensorSupported = false;
     @VisibleForTesting
     SurfaceSizeDefinition mSurfaceSizeDefinition;
@@ -175,6 +179,11 @@
             generate10BitSupportedCombinationList();
         }
 
+        mIsStreamUseCaseSupported = StreamUseCaseUtil.isStreamUseCaseSupported(mCharacteristics);
+        if (mIsStreamUseCaseSupported) {
+            generateStreamUseCaseSupportedCombinationList();
+        }
+
         generateSurfaceSizeDefinition();
         checkCustomization();
     }
@@ -217,6 +226,25 @@
         return isSupported;
     }
 
+    @Nullable
+    SurfaceCombination getSupportedStreamUseCaseSurfaceCombination(
+            @NonNull FeatureSettings featureSettings,
+            List<SurfaceConfig> surfaceConfigList) {
+        if (!StreamUseCaseUtil.shouldUseStreamUseCase(featureSettings)) {
+            return null;
+        }
+
+        for (SurfaceCombination surfaceCombination : mSurfaceCombinationsStreamUseCase) {
+            // Stream use case table doesn't support sublist. SurfaceCombination and
+            // SurfaceConfig list need to be EXACT match.
+            if (surfaceCombination.getSurfaceConfigList().size() == surfaceConfigList.size()
+                    && surfaceCombination.isSupported(surfaceConfigList)) {
+                return surfaceCombination;
+            }
+        }
+        return null;
+    }
+
     /**
      * Returns the supported surface combinations according to the specified feature
      * settings.
@@ -503,7 +531,6 @@
      *                                  of {@link DynamicRange}, or requiring an
      *                                  unsupported combination of camera features.
      */
-    @OptIn(markerClass = ExperimentalCamera2Interop.class)
     @NonNull
     Pair<Map<UseCaseConfig<?>, StreamSpec>, Map<AttachedSurfaceInfo, StreamSpec>>
             getSuggestedStreamSpecifications(
@@ -547,7 +574,16 @@
                             getUpdatedSurfaceSizeDefinitionByFormat(imageFormat)));
         }
 
-        if (!checkSupported(featureSettings, surfaceConfigs)) {
+        boolean containsZsl = StreamUseCaseUtil.containsZslUseCase(attachedSurfaces,
+                newUseCaseConfigs);
+        SurfaceCombination surfaceCombinationForStreamUseCase =
+                mIsStreamUseCaseSupported && !containsZsl
+                        ? getSupportedStreamUseCaseSurfaceCombination(featureSettings,
+                        surfaceConfigs) : null;
+
+        boolean isSurfaceCombinationSupported = checkSupported(featureSettings, surfaceConfigs);
+
+        if (surfaceCombinationForStreamUseCase == null && !isSurfaceCombinationSupported) {
             throw new IllegalArgumentException(
                     "No supported surface combination is found for camera device - Id : "
                             + mCameraId + ".  May be attempting to bind too many use cases. "
@@ -593,42 +629,53 @@
                             targetFramerateForConfig);
         }
 
-        Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecMap;
+        Map<AttachedSurfaceInfo, StreamSpec> attachedSurfaceStreamSpecMap = new HashMap<>();
+        Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecMap = new HashMap<>();
+
         List<Size> savedSizes = null;
         int savedConfigMaxFps = Integer.MAX_VALUE;
+        List<Size> savedSizesForStreamUseCase = null;
+        int savedConfigMaxFpsForStreamUseCase = Integer.MAX_VALUE;
+
+        if (surfaceCombinationForStreamUseCase != null) {
+            // Check if any possible size arrangement is supported for stream use case.
+            for (List<Size> possibleSizeList : allPossibleSizeArrangements) {
+                List<SurfaceConfig> surfaceConfigList = getSurfaceConfigListAndFpsCeiling(
+                        cameraMode,
+                        attachedSurfaces, possibleSizeList, newUseCaseConfigs,
+                        useCasesPriorityOrder, existingSurfaceFrameRateCeiling).first;
+                surfaceCombinationForStreamUseCase =
+                        getSupportedStreamUseCaseSurfaceCombination(featureSettings,
+                                surfaceConfigList);
+                if (surfaceCombinationForStreamUseCase != null) {
+                    break;
+                }
+            }
+
+            // We can terminate early if surface combination is not supported and none of the
+            // possible size arrangement supports stream use case either.
+            if (surfaceCombinationForStreamUseCase == null && !isSurfaceCombinationSupported) {
+                throw new IllegalArgumentException(
+                        "No supported surface combination is found for camera device - Id : "
+                                + mCameraId + ".  May be attempting to bind too many use cases. "
+                                + "Existing surfaces: " + attachedSurfaces + " New configs: "
+                                + newUseCaseConfigs);
+            }
+        }
+
+        boolean supportedSizesFound = false;
+        boolean supportedSizesForStreamUseCaseFound = false;
 
         // Transform use cases to SurfaceConfig list and find the first (best) workable combination
         for (List<Size> possibleSizeList : allPossibleSizeArrangements) {
             // Attach SurfaceConfig of original use cases since it will impact the new use cases
-            List<SurfaceConfig> surfaceConfigList = new ArrayList<>();
-            int currentConfigFramerateCeiling = existingSurfaceFrameRateCeiling;
+            Pair<List<SurfaceConfig>, Integer> resultPair =
+                    getSurfaceConfigListAndFpsCeiling(cameraMode,
+                            attachedSurfaces, possibleSizeList, newUseCaseConfigs,
+                            useCasesPriorityOrder, existingSurfaceFrameRateCeiling);
+            List<SurfaceConfig> surfaceConfigList = resultPair.first;
+            int currentConfigFramerateCeiling = resultPair.second;
             boolean isConfigFrameRateAcceptable = true;
-
-            for (AttachedSurfaceInfo attachedSurfaceInfo : attachedSurfaces) {
-                surfaceConfigList.add(attachedSurfaceInfo.getSurfaceConfig());
-            }
-
-            // Attach SurfaceConfig of new use cases
-            for (int i = 0; i < possibleSizeList.size(); i++) {
-                Size size = possibleSizeList.get(i);
-                UseCaseConfig<?> newUseCase =
-                        newUseCaseConfigs.get(useCasesPriorityOrder.get(i));
-                int imageFormat = newUseCase.getInputFormat();
-                // add new use case/size config to list of surfaces
-                surfaceConfigList.add(
-                        SurfaceConfig.transformSurfaceConfig(
-                                cameraMode,
-                                imageFormat,
-                                size,
-                                getUpdatedSurfaceSizeDefinitionByFormat(imageFormat)));
-
-                // get the maximum fps of the new surface and update the maximum fps of the
-                // proposed configuration
-                currentConfigFramerateCeiling = getUpdatedMaximumFps(
-                        currentConfigFramerateCeiling,
-                        newUseCase.getInputFormat(),
-                        size);
-            }
             if (targetFramerateForConfig != null) {
                 if (existingSurfaceFrameRateCeiling > currentConfigFramerateCeiling
                         && currentConfigFramerateCeiling < targetFramerateForConfig.getLower()) {
@@ -640,8 +687,11 @@
                 }
             }
 
+            // Find the same possible size arrangement that is supported by stream use case again
+            // if we found one earlier.
+
             // only change the saved config if you get another that has a better max fps
-            if (checkSupported(featureSettings, surfaceConfigList)) {
+            if (!supportedSizesFound && checkSupported(featureSettings, surfaceConfigList)) {
                 // if the config is supported by the device but doesn't meet the target framerate,
                 // save the config
                 if (savedConfigMaxFps == Integer.MAX_VALUE) {
@@ -657,7 +707,34 @@
                 if (isConfigFrameRateAcceptable) {
                     savedConfigMaxFps = currentConfigFramerateCeiling;
                     savedSizes = possibleSizeList;
-                    break;
+                    supportedSizesFound = true;
+                    if (supportedSizesForStreamUseCaseFound) {
+                        break;
+                    }
+                }
+            }
+            // If we already know that there is a supported surface combination from the stream
+            // use case table, keep an independent tracking on the saved sizes and max FPS. Only
+            // use stream use case if the save sizes for the normal case and for stream use case
+            // are the same.
+            if (surfaceCombinationForStreamUseCase != null && !supportedSizesForStreamUseCaseFound
+                    && getSupportedStreamUseCaseSurfaceCombination(
+                    featureSettings, surfaceConfigList) != null) {
+                if (savedConfigMaxFpsForStreamUseCase == Integer.MAX_VALUE) {
+                    savedConfigMaxFpsForStreamUseCase = currentConfigFramerateCeiling;
+                    savedSizesForStreamUseCase = possibleSizeList;
+                } else if (savedConfigMaxFpsForStreamUseCase < currentConfigFramerateCeiling) {
+                    savedConfigMaxFpsForStreamUseCase = currentConfigFramerateCeiling;
+                    savedSizesForStreamUseCase = possibleSizeList;
+                }
+
+                if (isConfigFrameRateAcceptable) {
+                    savedConfigMaxFpsForStreamUseCase = currentConfigFramerateCeiling;
+                    savedSizesForStreamUseCase = possibleSizeList;
+                    supportedSizesForStreamUseCaseFound = true;
+                    if (supportedSizesFound) {
+                        break;
+                    }
                 }
             }
         }
@@ -670,7 +747,6 @@
                         getClosestSupportedDeviceFrameRate(targetFramerateForConfig,
                                 savedConfigMaxFps);
             }
-            suggestedStreamSpecMap = new HashMap<>();
             for (UseCaseConfig<?> useCaseConfig : newUseCaseConfigs) {
                 Size resolutionForUseCase = savedSizes.get(
                         useCasesPriorityOrder.indexOf(newUseCaseConfigs.indexOf(useCaseConfig)));
@@ -693,11 +769,67 @@
                             + " Existing surfaces: " + attachedSurfaces
                             + " New configs: " + newUseCaseConfigs);
         }
-        Map<AttachedSurfaceInfo, StreamSpec> attachedSurfaceStreamSpecMap = new HashMap<>();
-        // TODO(b/266879290): Invoke StreamUSeCaseUtil.populateStreamUseCaseStreamSpecOption()
+
+        // Only perform stream use case operations if the saved max FPS and sizes are the same
+        if (surfaceCombinationForStreamUseCase != null
+                && savedConfigMaxFps == savedConfigMaxFpsForStreamUseCase
+                && savedSizes.size() == savedSizesForStreamUseCase.size()) {
+            boolean hasDifferenceSavedSizes = false;
+            for (int i = 0; i < savedSizes.size(); i++) {
+                if (!savedSizes.get(i).equals(savedSizesForStreamUseCase.get(i))) {
+                    hasDifferenceSavedSizes = true;
+                    break;
+                }
+            }
+            if (!hasDifferenceSavedSizes) {
+                boolean hasStreamUseCaseOverride =
+                        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOptionWithInteropOverride(
+                                mCharacteristics, attachedSurfaces, suggestedStreamSpecMap,
+                                attachedSurfaceStreamSpecMap);
+                if (!hasStreamUseCaseOverride) {
+                    // TODO(b/280335430): Use surfaceCombinationForStreamUseCase to populate the
+                    //  streamSpec maps
+                }
+            }
+        }
+
         return new Pair<>(suggestedStreamSpecMap, attachedSurfaceStreamSpecMap);
     }
 
+    private Pair<List<SurfaceConfig>, Integer> getSurfaceConfigListAndFpsCeiling(
+            @CameraMode.Mode int cameraMode,
+            List<AttachedSurfaceInfo> attachedSurfaces,
+            List<Size> possibleSizeList, List<UseCaseConfig<?>> newUseCaseConfigs,
+            List<Integer> useCasesPriorityOrder,
+            int currentConfigFramerateCeiling) {
+        List<SurfaceConfig> surfaceConfigList = new ArrayList<>();
+        for (AttachedSurfaceInfo attachedSurfaceInfo : attachedSurfaces) {
+            surfaceConfigList.add(attachedSurfaceInfo.getSurfaceConfig());
+        }
+
+        // Attach SurfaceConfig of new use cases
+        for (int i = 0; i < possibleSizeList.size(); i++) {
+            Size size = possibleSizeList.get(i);
+            UseCaseConfig<?> newUseCase =
+                    newUseCaseConfigs.get(useCasesPriorityOrder.get(i));
+            int imageFormat = newUseCase.getInputFormat();
+            // add new use case/size config to list of surfaces
+            surfaceConfigList.add(
+                    SurfaceConfig.transformSurfaceConfig(
+                            cameraMode,
+                            imageFormat,
+                            size,
+                            getUpdatedSurfaceSizeDefinitionByFormat(imageFormat)));
+            // get the maximum fps of the new surface and update the maximum fps of the
+            // proposed configuration
+            currentConfigFramerateCeiling = getUpdatedMaximumFps(
+                    currentConfigFramerateCeiling,
+                    newUseCase.getInputFormat(),
+                    size);
+        }
+        return new Pair<>(surfaceConfigList, currentConfigFramerateCeiling);
+    }
+
     /**
      * Applies resolution selection order related workarounds.
      *
@@ -925,6 +1057,11 @@
                 GuaranteedConfigurationsUtil.get10BitSupportedCombinationList());
     }
 
+    private void generateStreamUseCaseSupportedCombinationList() {
+        mSurfaceCombinationsStreamUseCase.addAll(
+                GuaranteedConfigurationsUtil.getStreamUseCaseSupportedCombinationList());
+    }
+
     private void checkCustomization() {
         // TODO(b/119466260): Integrate found feasible stream combinations into supported list
     }
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
index 3919413..7439f09 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
@@ -323,122 +323,63 @@
 
     @SdkSuppress(minSdkVersion = 33)
     @Test
-    public void populateStreamUseCaseStreamSpecOption_streamUseCaseNotAvailable() {
-        Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecMap = new HashMap<>();
-        UseCaseConfig<?> useCaseConfig = getFakeUseCaseConfigWithOptions(true, false, false,
-                UseCaseConfigFactory.CaptureType.PREVIEW, ImageFormat.PRIVATE);
-        suggestedStreamSpecMap.put(useCaseConfig,
-                getFakeStreamSpecFromFakeUseCaseConfig(useCaseConfig));
-        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOption(
-                getCameraCharacteristicsCompat(true),
-                SupportedSurfaceCombination.FeatureSettings.of(CameraMode.DEFAULT,
-                        DynamicRange.BIT_DEPTH_8_BIT), new ArrayList<>(), suggestedStreamSpecMap,
-                new HashMap<>());
-        assertFalse(suggestedStreamSpecMap.get(useCaseConfig)
-                .getImplementationOptions().containsOption(
-                        STREAM_USE_CASE_STREAM_SPEC_OPTION));
+    public void isStreamUseCaseSupported_streamUseCaseNotAvailable() {
+        assertFalse(StreamUseCaseUtil.isStreamUseCaseSupported(
+                getCameraCharacteristicsCompat(true)));
     }
 
     @SdkSuppress(minSdkVersion = 33)
     @Test
-    public void populateStreamUseCaseStreamSpecOption_cameraModeNotSupported() {
-        Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecMap = new HashMap<>();
-        UseCaseConfig<?> useCaseConfig = getFakeUseCaseConfigWithOptions(true, false, false,
-                UseCaseConfigFactory.CaptureType.PREVIEW, ImageFormat.PRIVATE);
-        suggestedStreamSpecMap.put(useCaseConfig,
-                getFakeStreamSpecFromFakeUseCaseConfig(useCaseConfig));
-        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOption(
-                getCameraCharacteristicsCompat(),
+    public void shouldUseStreamUseCase_cameraModeNotSupported() {
+        assertFalse(StreamUseCaseUtil.shouldUseStreamUseCase(
                 SupportedSurfaceCombination.FeatureSettings.of(CameraMode.CONCURRENT_CAMERA,
-                        DynamicRange.BIT_DEPTH_8_BIT), new ArrayList<>(), suggestedStreamSpecMap,
-                new HashMap<>());
-        assertFalse(suggestedStreamSpecMap.get(useCaseConfig)
-                .getImplementationOptions().containsOption(
-                        STREAM_USE_CASE_STREAM_SPEC_OPTION));
+                        DynamicRange.BIT_DEPTH_8_BIT)));
     }
 
     @SdkSuppress(minSdkVersion = 33)
     @Test
-    public void populateStreamUseCaseStreamSpecOption_bitDepthNotSupported() {
-        Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecMap = new HashMap<>();
-        UseCaseConfig<?> useCaseConfig = getFakeUseCaseConfigWithOptions(true, false, false,
-                UseCaseConfigFactory.CaptureType.PREVIEW, ImageFormat.PRIVATE);
-        suggestedStreamSpecMap.put(useCaseConfig,
-                getFakeStreamSpecFromFakeUseCaseConfig(useCaseConfig));
-        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOption(
-                getCameraCharacteristicsCompat(),
+    public void shouldUseStreamUseCase_bitDepthNotSupported() {
+        assertFalse(StreamUseCaseUtil.shouldUseStreamUseCase(
                 SupportedSurfaceCombination.FeatureSettings.of(CameraMode.DEFAULT,
-                        DynamicRange.BIT_DEPTH_10_BIT), new ArrayList<>(), suggestedStreamSpecMap,
-                new HashMap<>());
-        assertFalse(suggestedStreamSpecMap.get(useCaseConfig)
-                .getImplementationOptions().containsOption(
-                        STREAM_USE_CASE_STREAM_SPEC_OPTION));
+                        DynamicRange.BIT_DEPTH_10_BIT)));
     }
 
     @SdkSuppress(minSdkVersion = 33)
     @Test
-    public void populateStreamUseCaseStreamSpecOption_isZslUseCase() {
-        Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecMap = new HashMap<>();
+    public void containsZslUseCase_isZslUseCase() {
         UseCaseConfig<?> useCaseConfig = getFakeUseCaseConfigWithOptions(true, false, true,
                 UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE, ImageFormat.JPEG);
-        suggestedStreamSpecMap.put(useCaseConfig,
-                getFakeStreamSpecFromFakeUseCaseConfig(useCaseConfig));
-        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOption(getCameraCharacteristicsCompat(),
-                SupportedSurfaceCombination.FeatureSettings.of(CameraMode.DEFAULT,
-                        DynamicRange.BIT_DEPTH_8_BIT), new ArrayList<>(), suggestedStreamSpecMap,
-                new HashMap<>());
-        assertFalse(suggestedStreamSpecMap.get(useCaseConfig)
-                .getImplementationOptions().containsOption(
-                        STREAM_USE_CASE_STREAM_SPEC_OPTION));
+        List<UseCaseConfig<?>> useCaseConfigList = new ArrayList<>();
+        useCaseConfigList.add(useCaseConfig);
+        assertTrue(StreamUseCaseUtil.containsZslUseCase(new ArrayList<>(), useCaseConfigList));
     }
 
     @SdkSuppress(minSdkVersion = 33)
     @Test
-    public void populateStreamUseCaseStreamSpecOption_isZslUseCase_ZslDisabled() {
-        Map<UseCaseConfig<?>, StreamSpec> suggestedStreamSpecMap = new HashMap<>();
+    public void containsZslUseCase_isZslUseCase_ZslDisabled() {
         UseCaseConfig<?> useCaseConfig = getFakeUseCaseConfigWithOptions(true, true, true,
                 UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE, ImageFormat.JPEG);
-        suggestedStreamSpecMap.put(useCaseConfig,
-                getFakeStreamSpecFromFakeUseCaseConfig(useCaseConfig));
-        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOption(getCameraCharacteristicsCompat(),
-                SupportedSurfaceCombination.FeatureSettings.of(CameraMode.DEFAULT,
-                        DynamicRange.BIT_DEPTH_8_BIT), new ArrayList<>(), suggestedStreamSpecMap,
-                new HashMap<>());
-        assertTrue(suggestedStreamSpecMap.get(
-                useCaseConfig).getImplementationOptions().retrieveOption(
-                STREAM_USE_CASE_STREAM_SPEC_OPTION) == TEST_STREAM_USE_CASE_OPTION_VALUE);
+        List<UseCaseConfig<?>> useCaseConfigList = new ArrayList<>();
+        useCaseConfigList.add(useCaseConfig);
+        assertFalse(StreamUseCaseUtil.containsZslUseCase(new ArrayList<>(), useCaseConfigList));
     }
 
     @SdkSuppress(minSdkVersion = 33)
     @Test
-    public void populateStreamUseCaseStreamSpecOption_isZslSurface() {
+    public void containsZslUseCase_isZslSurface() {
         List<AttachedSurfaceInfo> attachedSurfaces = new ArrayList<>();
         attachedSurfaces.add(getFakeAttachedSurfaceInfo(true, false, true,
                 UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE, ImageFormat.JPEG));
-        Map<AttachedSurfaceInfo, StreamSpec> attachedSurfaceStreamSpecMap = new HashMap<>();
-        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOption(getCameraCharacteristicsCompat(),
-                SupportedSurfaceCombination.FeatureSettings.of(CameraMode.DEFAULT,
-                        DynamicRange.BIT_DEPTH_8_BIT), attachedSurfaces, new HashMap<>(),
-                attachedSurfaceStreamSpecMap
-        );
-        assertFalse(attachedSurfaceStreamSpecMap.containsKey(attachedSurfaces.get(0)));
+        assertTrue(StreamUseCaseUtil.containsZslUseCase(attachedSurfaces, new ArrayList<>()));
     }
 
     @SdkSuppress(minSdkVersion = 33)
     @Test
-    public void populateStreamUseCaseStreamSpecOption_isZslSurface_ZslDisabled() {
+    public void containsZslUseCase_isZslSurface_ZslDisabled() {
         List<AttachedSurfaceInfo> attachedSurfaces = new ArrayList<>();
         attachedSurfaces.add(getFakeAttachedSurfaceInfo(true, true, true,
                 UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE, ImageFormat.JPEG));
-        Map<AttachedSurfaceInfo, StreamSpec> attachedSurfaceStreamSpecMap = new HashMap<>();
-        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOption(getCameraCharacteristicsCompat(),
-                SupportedSurfaceCombination.FeatureSettings.of(CameraMode.DEFAULT,
-                        DynamicRange.BIT_DEPTH_8_BIT), attachedSurfaces, new HashMap<>(),
-                attachedSurfaceStreamSpecMap
-        );
-        assertTrue(attachedSurfaceStreamSpecMap.get(
-                attachedSurfaces.get(0)).getImplementationOptions().retrieveOption(
-                STREAM_USE_CASE_STREAM_SPEC_OPTION) == TEST_STREAM_USE_CASE_OPTION_VALUE);
+        assertFalse(StreamUseCaseUtil.containsZslUseCase(attachedSurfaces, new ArrayList<>()));
     }
 
     @SdkSuppress(minSdkVersion = 33)
@@ -449,9 +390,8 @@
                 UseCaseConfigFactory.CaptureType.PREVIEW, ImageFormat.PRIVATE);
         suggestedStreamSpecMap.put(useCaseConfig,
                 getFakeStreamSpecFromFakeUseCaseConfig(useCaseConfig));
-        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOption(getCameraCharacteristicsCompat(),
-                SupportedSurfaceCombination.FeatureSettings.of(CameraMode.DEFAULT,
-                        DynamicRange.BIT_DEPTH_8_BIT), new ArrayList<>(), suggestedStreamSpecMap,
+        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOptionWithInteropOverride(
+                getCameraCharacteristicsCompat(), new ArrayList<>(), suggestedStreamSpecMap,
                 new HashMap<>());
         assertTrue(suggestedStreamSpecMap.get(
                 useCaseConfig).getImplementationOptions().retrieveOption(
@@ -465,9 +405,8 @@
         attachedSurfaces.add(getFakeAttachedSurfaceInfo(true, false, false,
                 UseCaseConfigFactory.CaptureType.PREVIEW, ImageFormat.PRIVATE));
         Map<AttachedSurfaceInfo, StreamSpec> attachedSurfaceStreamSpecMap = new HashMap<>();
-        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOption(getCameraCharacteristicsCompat(),
-                SupportedSurfaceCombination.FeatureSettings.of(CameraMode.DEFAULT,
-                        DynamicRange.BIT_DEPTH_8_BIT), attachedSurfaces, new HashMap<>(),
+        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOptionWithInteropOverride(
+                getCameraCharacteristicsCompat(), attachedSurfaces, new HashMap<>(),
                 attachedSurfaceStreamSpecMap
         );
         assertTrue(attachedSurfaceStreamSpecMap.get(
@@ -487,9 +426,8 @@
         attachedSurfaces.add(getFakeAttachedSurfaceInfo(true, false, false,
                 UseCaseConfigFactory.CaptureType.PREVIEW, ImageFormat.PRIVATE));
         Map<AttachedSurfaceInfo, StreamSpec> attachedSurfaceStreamSpecMap = new HashMap<>();
-        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOption(getCameraCharacteristicsCompat(),
-                SupportedSurfaceCombination.FeatureSettings.of(CameraMode.DEFAULT,
-                        DynamicRange.BIT_DEPTH_8_BIT), attachedSurfaces, suggestedStreamSpecMap,
+        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOptionWithInteropOverride(
+                getCameraCharacteristicsCompat(), attachedSurfaces, suggestedStreamSpecMap,
                 attachedSurfaceStreamSpecMap
         );
         assertTrue(suggestedStreamSpecMap.get(
@@ -511,9 +449,8 @@
         attachedSurfaces.add(getFakeAttachedSurfaceInfo(true, false, false,
                 UseCaseConfigFactory.CaptureType.PREVIEW, ImageFormat.PRIVATE));
         Map<AttachedSurfaceInfo, StreamSpec> attachedSurfaceStreamSpecMap = new HashMap<>();
-        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOption(getCameraCharacteristicsCompat(),
-                SupportedSurfaceCombination.FeatureSettings.of(CameraMode.DEFAULT,
-                        DynamicRange.BIT_DEPTH_8_BIT), attachedSurfaces, suggestedStreamSpecMap,
+        StreamUseCaseUtil.populateStreamUseCaseStreamSpecOptionWithInteropOverride(
+                getCameraCharacteristicsCompat(), attachedSurfaces, suggestedStreamSpecMap,
                 attachedSurfaceStreamSpecMap
         );
     }
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
index d0ea2aa..f847397 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
@@ -38,10 +38,12 @@
 import android.media.CamcorderProfile.QUALITY_720P
 import android.media.MediaRecorder
 import android.os.Build
+import android.util.Pair
 import android.util.Range
 import android.util.Size
 import android.view.WindowManager
 import androidx.camera.camera2.Camera2Config
+import androidx.camera.camera2.impl.Camera2ImplConfig
 import androidx.camera.camera2.internal.SupportedSurfaceCombination.FeatureSettings
 import androidx.camera.camera2.internal.compat.CameraManagerCompat
 import androidx.camera.core.CameraSelector.LensFacing
@@ -109,6 +111,7 @@
 private const val DEFAULT_CAMERA_ID = "0"
 private const val EXTERNAL_CAMERA_ID = "0-external"
 private const val SENSOR_ORIENTATION_90 = 90
+private const val STREAM_USE_CASE_OVERRIDE = 3L
 private val LANDSCAPE_PIXEL_ARRAY_SIZE = Size(4032, 3024)
 private val DISPLAY_SIZE = Size(720, 1280)
 private val PREVIEW_SIZE = Size(1280, 720)
@@ -1539,13 +1542,13 @@
         cameraMode: Int = CameraMode.DEFAULT,
         dynamicRangeProfiles: DynamicRangeProfiles? = null,
         default10BitProfile: Long? = null,
-        useCasesExpectedDynamicRangeMap: Map<UseCase, DynamicRange> = emptyMap()
-    ) {
+        useCasesExpectedDynamicRangeMap: Map<UseCase, DynamicRange> = emptyMap(),
+    ): Pair<Map<UseCaseConfig<*>, StreamSpec>, Map<AttachedSurfaceInfo, StreamSpec>> {
         setupCameraAndInitCameraX(
             hardwareLevel = hardwareLevel,
             capabilities = capabilities,
             dynamicRangeProfiles = dynamicRangeProfiles,
-            default10BitProfile = default10BitProfile
+            default10BitProfile = default10BitProfile,
         )
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
@@ -1554,14 +1557,17 @@
         val useCaseConfigMap = getUseCaseToConfigMap(useCasesExpectedSizeMap.keys.toList())
         val useCaseConfigToOutputSizesMap =
             getUseCaseConfigToOutputSizesMap(useCaseConfigMap.values.toList())
-        val suggestedStreamSpecs = supportedSurfaceCombination.getSuggestedStreamSpecifications(
+        val resultPair = supportedSurfaceCombination.getSuggestedStreamSpecifications(
             cameraMode,
             attachedSurfaceInfoList,
             useCaseConfigToOutputSizesMap
-        ).first
+        )
+        val suggestedStreamSpecsForNewUseCases = resultPair.first
+        val suggestedStreamSpecsForOldSurfaces = resultPair.second
 
+        var hasStreamUseCaseStreamSpecOption: Boolean? = null
         useCasesExpectedSizeMap.keys.forEach {
-            val resultSize = suggestedStreamSpecs[useCaseConfigMap[it]]!!.resolution
+            val resultSize = suggestedStreamSpecsForNewUseCases[useCaseConfigMap[it]]!!.resolution
             val expectedSize = useCasesExpectedSizeMap[it]!!
             if (!compareWithAtMost) {
                 assertThat(resultSize).isEqualTo(expectedSize)
@@ -1571,18 +1577,59 @@
 
             if (compareExpectedFps != null) {
                 assertThat(
-                    suggestedStreamSpecs[useCaseConfigMap[it]]!!.expectedFrameRateRange
-                        == compareExpectedFps
+                    suggestedStreamSpecsForNewUseCases[useCaseConfigMap[it]]!!
+                        .expectedFrameRateRange == compareExpectedFps
                 )
             }
         }
 
         useCasesExpectedDynamicRangeMap.keys.forEach {
-            val resultDynamicRange = suggestedStreamSpecs[useCaseConfigMap[it]]!!.dynamicRange
+            val resultDynamicRange =
+                suggestedStreamSpecsForNewUseCases[useCaseConfigMap[it]]!!.dynamicRange
             val expectedDynamicRange = useCasesExpectedDynamicRangeMap[it]
 
             assertThat(resultDynamicRange).isEqualTo(expectedDynamicRange)
         }
+
+        // Assert that if one stream specification has stream use case options, all other
+        // stream specifications also have it.
+        suggestedStreamSpecsForNewUseCases.entries.forEach {
+            if (it.value.implementationOptions?.containsOption(
+                    StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION
+                ) == true
+            ) {
+                if (hasStreamUseCaseStreamSpecOption != null) {
+                    assertThat(hasStreamUseCaseStreamSpecOption).isTrue()
+                } else {
+                    hasStreamUseCaseStreamSpecOption = true
+                }
+            } else {
+                if (hasStreamUseCaseStreamSpecOption != null) {
+                    assertThat(hasStreamUseCaseStreamSpecOption).isFalse()
+                } else {
+                    hasStreamUseCaseStreamSpecOption = false
+                }
+            }
+        }
+        suggestedStreamSpecsForOldSurfaces.entries.forEach {
+            if (it.value.implementationOptions?.containsOption(
+                    StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION
+                ) == true
+            ) {
+                if (hasStreamUseCaseStreamSpecOption != null) {
+                    assertThat(hasStreamUseCaseStreamSpecOption).isTrue()
+                } else {
+                    hasStreamUseCaseStreamSpecOption = true
+                }
+            } else {
+                if (hasStreamUseCaseStreamSpecOption != null) {
+                    assertThat(hasStreamUseCaseStreamSpecOption).isFalse()
+                } else {
+                    hasStreamUseCaseStreamSpecOption = false
+                }
+            }
+        }
+        return resultPair
     }
 
     private fun getUseCaseToConfigMap(useCases: List<UseCase>): Map<UseCase, UseCaseConfig<*>> {
@@ -2958,6 +3005,86 @@
         assertThat(resultList).containsExactlyElementsIn(expectedResultList).inOrder()
     }
 
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canPopulateStreamUseCaseStreamSpecOption_jpeg() {
+        val jpegUseCase =
+            createUseCase(CaptureType.IMAGE_CAPTURE, streamUseCaseOverride = true) // JPEG
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(jpegUseCase, MAXIMUM_SIZE)
+        }
+        val resultPair = getSuggestedSpecsAndVerify(useCaseExpectedResultMap)
+        assertThat(resultPair.first.size).isEqualTo(1)
+        assertThat(
+            resultPair.first[jpegUseCase.currentConfig]!!.implementationOptions!!.retrieveOption(
+                StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION
+            )
+        ).isEqualTo(STREAM_USE_CASE_OVERRIDE)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun throwException_PopulateStreamUseCaseStreamSpecOption_notFullyOverride() {
+        val jpegUseCase =
+            createUseCase(CaptureType.IMAGE_CAPTURE, streamUseCaseOverride = true) // JPEG
+        val yuvUseCase =
+            createUseCase(CaptureType.PREVIEW, streamUseCaseOverride = false) // PREVIEW
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(jpegUseCase, MAXIMUM_SIZE)
+            put(yuvUseCase, PREVIEW_SIZE)
+        }
+        assertThrows(IllegalArgumentException::class.java) {
+            getSuggestedSpecsAndVerify(useCaseExpectedResultMap)
+        }
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun skipPopulateStreamUseCaseStreamSpecOption_unsupportedCombination() {
+        val useCase1 =
+            createUseCase(CaptureType.PREVIEW, streamUseCaseOverride = true) // PREVIEW
+        val useCase2 =
+            createUseCase(CaptureType.PREVIEW, streamUseCaseOverride = true) // PREVIEW
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, PREVIEW_SIZE)
+            put(useCase2, PREVIEW_SIZE)
+        }
+        // PRIV + PRIV is supported by the Ultra-high table but not Stream use case
+        val resultPair = getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap, cameraMode = CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA,
+        )
+        assertThat(resultPair.first.size).isEqualTo(2)
+        assertThat(
+            resultPair.first[useCase1.currentConfig]!!.implementationOptions!!.containsOption(
+                StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION
+            )
+        ).isFalse()
+        assertThat(
+            resultPair.first[useCase2.currentConfig]!!.implementationOptions!!.containsOption(
+                StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION
+            )
+        ).isFalse()
+    }
+
+    @Config(minSdk = 21, maxSdk = 32)
+    @Test
+    fun skipPopulateStreamUseCaseStreamSpecOption_unsupportedOs() {
+        val jpegUseCase =
+            createUseCase(CaptureType.IMAGE_CAPTURE, streamUseCaseOverride = true) // JPEG
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(jpegUseCase, MAXIMUM_SIZE)
+        }
+        val resultPair = getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+        )
+        assertThat(resultPair.first.size).isEqualTo(1)
+        assertThat(
+            resultPair.first[jpegUseCase.currentConfig]!!.implementationOptions!!.containsOption(
+                StreamUseCaseUtil.STREAM_USE_CASE_STREAM_SPEC_OPTION
+            )
+        ).isFalse()
+    }
+
     /**
      * Sets up camera according to the specified settings and initialize [CameraX].
      *
@@ -2989,7 +3116,7 @@
         maximumResolutionHighResolutionSupportedSizes: Array<Size>? = null,
         dynamicRangeProfiles: DynamicRangeProfiles? = null,
         default10BitProfile: Long? = null,
-        capabilities: IntArray? = null
+        capabilities: IntArray? = null,
     ) {
         setupCamera(
             cameraId,
@@ -3002,7 +3129,7 @@
             maximumResolutionHighResolutionSupportedSizes,
             dynamicRangeProfiles,
             default10BitProfile,
-            capabilities
+            capabilities,
         )
 
         @LensFacing val lensFacingEnum = CameraUtil.getLensFacingEnumFromInt(
@@ -3062,7 +3189,7 @@
         maximumResolutionHighResolutionSupportedSizes: Array<Size>? = null,
         dynamicRangeProfiles: DynamicRangeProfiles? = null,
         default10BitProfile: Long? = null,
-        capabilities: IntArray? = null
+        capabilities: IntArray? = null,
     ) {
         val mockMap = Mockito.mock(StreamConfigurationMap::class.java).also { map ->
             supportedSizes?.let {
@@ -3195,6 +3322,20 @@
                 }
             }
 
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
+            ) {
+                val uc = longArrayOf(
+                    CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT.toLong(),
+                    CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW.toLong(),
+                    CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL
+                        .toLong(),
+                    CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE.toLong(),
+                    CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong(),
+                    CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD.toLong()
+                )
+                set(CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES, uc)
+            }
+
             capabilities?.let {
                 set(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES, it)
             }
@@ -3267,8 +3408,18 @@
         targetFrameRate: Range<Int>? = null,
         dynamicRange: DynamicRange = DynamicRange.UNSPECIFIED
     ): UseCase {
+        return createUseCase(captureType, targetFrameRate, dynamicRange, false)
+    }
+
+    private fun createUseCase(
+        captureType: CaptureType,
+        targetFrameRate: Range<Int>? = null,
+        dynamicRange: DynamicRange = DynamicRange.UNSPECIFIED,
+        streamUseCaseOverride: Boolean
+    ): UseCase {
         val builder = FakeUseCaseConfig.Builder(
             captureType, when (captureType) {
+                CaptureType.PREVIEW -> ImageFormat.PRIVATE
                 CaptureType.IMAGE_CAPTURE -> ImageFormat.JPEG
                 CaptureType.IMAGE_ANALYSIS -> ImageFormat.YUV_420_888
                 else -> INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
@@ -3283,6 +3434,13 @@
             dynamicRange
         )
 
+        if (streamUseCaseOverride) {
+            builder.mutableConfig.insertOption(
+                Camera2ImplConfig.STREAM_USE_CASE_OPTION,
+                STREAM_USE_CASE_OVERRIDE
+            )
+        }
+
         return builder.build()
     }