Merge "StreamUseCase new implementation in camera-camera2" into androidx-main
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java
index c57a1af..b895158 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java
@@ -51,6 +51,7 @@
 import android.media.Image;
 import android.media.ImageReader;
 import android.media.ImageReader.OnImageAvailableListener;
+import android.media.MediaCodec;
 import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -69,25 +70,25 @@
 import androidx.camera.camera2.internal.compat.quirk.ConfigureSurfaceToSecondarySessionFailQuirk;
 import androidx.camera.camera2.internal.compat.quirk.DeviceQuirks;
 import androidx.camera.camera2.internal.compat.quirk.PreviewOrientationIncorrectQuirk;
+import androidx.camera.core.ImageAnalysis;
+import androidx.camera.core.ImageCapture;
+import androidx.camera.core.Preview;
 import androidx.camera.core.impl.CameraCaptureCallback;
 import androidx.camera.core.impl.CameraCaptureCallbacks;
 import androidx.camera.core.impl.CameraCaptureResult;
 import androidx.camera.core.impl.CaptureConfig;
 import androidx.camera.core.impl.DeferrableSurface;
-import androidx.camera.core.impl.ImageAnalysisConfig;
-import androidx.camera.core.impl.ImageCaptureConfig;
 import androidx.camera.core.impl.ImmediateSurface;
 import androidx.camera.core.impl.MutableOptionsBundle;
-import androidx.camera.core.impl.PreviewConfig;
 import androidx.camera.core.impl.Quirks;
 import androidx.camera.core.impl.SessionConfig;
-import androidx.camera.core.impl.UseCaseConfig;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.core.impl.utils.futures.FutureCallback;
 import androidx.camera.core.impl.utils.futures.Futures;
 import androidx.camera.testing.CameraUtil;
-import androidx.camera.testing.fakes.FakeUseCaseConfig;
+import androidx.camera.testing.fakes.FakeUseCase;
 import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.concurrent.futures.ResolvableFuture;
 import androidx.core.os.HandlerCompat;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -101,7 +102,6 @@
 import org.junit.AssumptionViolatedException;
 import org.junit.Before;
 import org.junit.BeforeClass;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -114,10 +114,11 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
@@ -158,6 +159,16 @@
     private final List<CaptureSession> mCaptureSessions = new ArrayList<>();
     private final List<DeferrableSurface> mDeferrableSurfaces = new ArrayList<>();
 
+    DeferrableSurface mMockSurface = new DeferrableSurface() {
+        private final ListenableFuture<Surface> mSurfaceFuture = ResolvableFuture.create();
+        @NonNull
+        @Override
+        protected ListenableFuture<Surface> provideSurface() {
+            // Return a never complete future.
+            return mSurfaceFuture;
+        }
+    };
+
     @Rule
     public TestRule getUseCameraRule() {
         if (SDK_INT >= 19) {
@@ -232,6 +243,7 @@
 
         mTestParameters0.tearDown();
         mTestParameters1.tearDown();
+        mDeferrableSurfaces.add(mMockSurface);
         for (DeferrableSurface deferrableSurface : mDeferrableSurfaces) {
             deferrableSurface.close();
         }
@@ -300,7 +312,7 @@
     }
 
     // Set stream use case is not supported before API 33
-    @SdkSuppress(maxSdkVersion = 32)
+    @SdkSuppress(maxSdkVersion = 32, minSdkVersion = 21)
     @Test
     public void setStreamUseCaseNotSupported() {
         ImageReader imageReader0 = ImageReader.newInstance(640, 480, ImageFormat.YUV_420_888, 2);
@@ -327,86 +339,121 @@
                 == CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW);
     }
 
-    @Ignore("b/259932467")
-    @SdkSuppress(maxSdkVersion = 32)
+    @SdkSuppress(maxSdkVersion = 32, minSdkVersion = 21)
     @Test
-    public void getStreamUseCaseFromUseCaseConfigsNotSupported() {
-        Collection<UseCaseConfig<?>> useCaseConfigs = new ArrayList<>();
-        useCaseConfigs.add(new FakeUseCaseConfig.Builder().getUseCaseConfig());
-        assertTrue(StreamUseCaseUtil.getStreamUseCaseFromUseCaseConfigs(useCaseConfigs,
-                new ArrayList<>())
-                == OutputConfigurationCompat.STREAM_USE_CASE_NONE);
+    public void getStreamUseCaseFromUseCaseNotSupported() {
+        Map<DeferrableSurface, Long> streamUseCaseMap = new HashMap<>();
+        mMockSurface.setContainerClass(Preview.class);
+        StreamUseCaseUtil.populateSurfaceToStreamUseCaseMapping(
+                new ArrayList<>(), streamUseCaseMap);
+        assertTrue(streamUseCaseMap.isEmpty());
     }
 
     @SdkSuppress(minSdkVersion = 33)
     @Test
-    public void getStreamUseCaseFromUseCaseConfigsEmptyUseCase() {
-        Collection<UseCaseConfig<?>> useCaseConfigs = new ArrayList<>();
-        assertTrue(StreamUseCaseUtil.getStreamUseCaseFromUseCaseConfigs(useCaseConfigs,
-                new ArrayList<>())
-                == CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT);
+    public void getStreamUseCaseFromUseCaseEmptyUseCase() {
+        Map<DeferrableSurface, Long> streamUseCaseMap = new HashMap<>();
+        StreamUseCaseUtil.populateSurfaceToStreamUseCaseMapping(
+                new ArrayList<>(), streamUseCaseMap);
+        assertTrue(streamUseCaseMap.isEmpty());
     }
 
     @SdkSuppress(minSdkVersion = 33)
     @Test
-    public void getStreamUseCaseFromUseCaseConfigsNoPreview() {
-        Collection<UseCaseConfig<?>> useCaseConfigs = new ArrayList<>();
-        useCaseConfigs.add(new FakeUseCaseConfig.Builder().getUseCaseConfig());
-        assertTrue(StreamUseCaseUtil.getStreamUseCaseFromUseCaseConfigs(useCaseConfigs,
-                new ArrayList<>())
-                == CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT);
+    public void getStreamUseCaseFromUseCaseNoPreview() {
+        Map<DeferrableSurface, Long> streamUseCaseMap = new HashMap<>();
+        mMockSurface.setContainerClass(FakeUseCase.class);
+        SessionConfig sessionConfig =
+                new SessionConfig.Builder()
+                        .addSurface(mMockSurface).build();
+        ArrayList<SessionConfig> sessionConfigs = new ArrayList<>();
+        sessionConfigs.add(sessionConfig);
+        StreamUseCaseUtil.populateSurfaceToStreamUseCaseMapping(
+                sessionConfigs, streamUseCaseMap);
+        assertTrue(streamUseCaseMap.isEmpty());
     }
 
     @SdkSuppress(minSdkVersion = 33)
     @Test
-    public void getStreamUseCaseFromUseCaseConfigsPreview() {
-        Collection<UseCaseConfig<?>> useCaseConfigs = new ArrayList<>();
-        PreviewConfig previewConfig = new PreviewConfig(MutableOptionsBundle.create());
-        useCaseConfigs.add(previewConfig);
-        assertTrue(StreamUseCaseUtil.getStreamUseCaseFromUseCaseConfigs(useCaseConfigs,
-                new ArrayList<>())
+    public void getStreamUseCaseFromUseCasePreview() {
+        Map<DeferrableSurface, Long> streamUseCaseMap = new HashMap<>();
+        mMockSurface.setContainerClass(Preview.class);
+        SessionConfig sessionConfig =
+                new SessionConfig.Builder()
+                        .addSurface(mMockSurface).build();
+        ArrayList<SessionConfig> sessionConfigs = new ArrayList<>();
+        sessionConfigs.add(sessionConfig);
+        StreamUseCaseUtil.populateSurfaceToStreamUseCaseMapping(
+                sessionConfigs, streamUseCaseMap);
+        assertTrue(streamUseCaseMap.get(mMockSurface)
                 == CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW);
     }
 
     @SdkSuppress(minSdkVersion = 33)
     @Test
-    public void getStreamUseCaseFromUseCaseConfigsZSL() {
-        Collection<UseCaseConfig<?>> useCaseConfigs = new ArrayList<>();
-        PreviewConfig previewConfig = new PreviewConfig(MutableOptionsBundle.create());
-        useCaseConfigs.add(previewConfig);
-        Collection<SessionConfig> sessionConfigs = new ArrayList<>();
-        sessionConfigs.add(new SessionConfig.Builder().setTemplateType(
-                CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG).build());
-        assertTrue(StreamUseCaseUtil.getStreamUseCaseFromUseCaseConfigs(useCaseConfigs,
-                sessionConfigs)
-                == CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT);
+    public void getStreamUseCaseFromUseCaseZSL() {
+        Map<DeferrableSurface, Long> streamUseCaseMap = new HashMap<>();
+        mMockSurface.setContainerClass(Preview.class);
+        SessionConfig sessionConfig =
+                new SessionConfig.Builder()
+                        .addSurface(mMockSurface)
+                        .setTemplateType(
+                                CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG).build();
+        ArrayList<SessionConfig> sessionConfigs = new ArrayList<>();
+        sessionConfigs.add(sessionConfig);
+        StreamUseCaseUtil.populateSurfaceToStreamUseCaseMapping(
+                sessionConfigs, streamUseCaseMap);
+        assertTrue(streamUseCaseMap.isEmpty());
     }
 
     @SdkSuppress(minSdkVersion = 33)
     @Test
-    public void getStreamUseCaseFromUseCaseConfigsAnalysis() {
-        Collection<UseCaseConfig<?>> useCaseConfigs = new ArrayList<>();
-        PreviewConfig previewConfig = new PreviewConfig(MutableOptionsBundle.create());
-        useCaseConfigs.add(previewConfig);
-        ImageAnalysisConfig analysisConfig = new ImageAnalysisConfig(MutableOptionsBundle.create());
-        useCaseConfigs.add(analysisConfig);
-        assertTrue(StreamUseCaseUtil.getStreamUseCaseFromUseCaseConfigs(useCaseConfigs,
-                new ArrayList<>())
-                == CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT);
+    public void getStreamUseCaseFromUseCaseImageAnalysis() {
+        Map<DeferrableSurface, Long> streamUseCaseMap = new HashMap<>();
+        mMockSurface.setContainerClass(ImageAnalysis.class);
+        SessionConfig sessionConfig =
+                new SessionConfig.Builder()
+                        .addSurface(mMockSurface).build();
+        ArrayList<SessionConfig> sessionConfigs = new ArrayList<>();
+        sessionConfigs.add(sessionConfig);
+        StreamUseCaseUtil.populateSurfaceToStreamUseCaseMapping(
+                sessionConfigs, streamUseCaseMap);
+        assertTrue(streamUseCaseMap.get(mMockSurface)
+                == CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW);
     }
 
     @SdkSuppress(minSdkVersion = 33)
     @Test
     public void getStreamUseCaseFromUseCaseConfigsImageCapture() {
-        Collection<UseCaseConfig<?>> useCaseConfigs = new ArrayList<>();
-        ImageCaptureConfig imageCaptureConfig = new ImageCaptureConfig(
-                MutableOptionsBundle.create());
-        useCaseConfigs.add(imageCaptureConfig);
-        assertTrue(StreamUseCaseUtil.getStreamUseCaseFromUseCaseConfigs(useCaseConfigs,
-                new ArrayList<>())
+        Map<DeferrableSurface, Long> streamUseCaseMap = new HashMap<>();
+        mMockSurface.setContainerClass(ImageCapture.class);
+        SessionConfig sessionConfig =
+                new SessionConfig.Builder()
+                        .addSurface(mMockSurface).build();
+        ArrayList<SessionConfig> sessionConfigs = new ArrayList<>();
+        sessionConfigs.add(sessionConfig);
+        StreamUseCaseUtil.populateSurfaceToStreamUseCaseMapping(
+                sessionConfigs, streamUseCaseMap);
+        assertTrue(streamUseCaseMap.get(mMockSurface)
                 == CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE);
     }
 
+    @SdkSuppress(minSdkVersion = 33)
+    @Test
+    public void getStreamUseCaseFromUseCaseConfigsVideoCapture() {
+        Map<DeferrableSurface, Long> streamUseCaseMap = new HashMap<>();
+        mMockSurface.setContainerClass(MediaCodec.class);
+        SessionConfig sessionConfig =
+                new SessionConfig.Builder()
+                        .addSurface(mMockSurface).build();
+        ArrayList<SessionConfig> sessionConfigs = new ArrayList<>();
+        sessionConfigs.add(sessionConfig);
+        StreamUseCaseUtil.populateSurfaceToStreamUseCaseMapping(
+                sessionConfigs, streamUseCaseMap);
+        assertTrue(streamUseCaseMap.get(mMockSurface)
+                == CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD);
+    }
+
     // Sharing surface of YUV format is supported since API 28
     @SdkSuppress(minSdkVersion = 28)
     @Test
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
index 6449428..5c049d2 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraImpl.java
@@ -36,7 +36,6 @@
 import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
-import androidx.camera.camera2.impl.Camera2ImplConfig;
 import androidx.camera.camera2.internal.annotation.CameraExecutor;
 import androidx.camera.camera2.internal.compat.ApiCompat;
 import androidx.camera.camera2.internal.compat.CameraAccessExceptionCompat;
@@ -78,6 +77,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -1127,13 +1127,12 @@
             return;
         }
 
-        if (!validatingBuilder.build().getImplementationOptions().containsOption(
-                Camera2ImplConfig.STREAM_USE_CASE_OPTION)) {
-            validatingBuilder.addImplementationOption(Camera2ImplConfig.STREAM_USE_CASE_OPTION,
-                    StreamUseCaseUtil.getStreamUseCaseFromUseCaseConfigs(
-                            mUseCaseAttachState.getAttachedUseCaseConfigs(),
-                            mUseCaseAttachState.getAttachedSessionConfigs()));
-        }
+        Map<DeferrableSurface, Long> streamUseCaseMap = new HashMap<>();
+        StreamUseCaseUtil.populateSurfaceToStreamUseCaseMapping(
+                mUseCaseAttachState.getAttachedSessionConfigs(),
+                streamUseCaseMap);
+
+        mCaptureSession.setStreamUseCaseMap(streamUseCaseMap);
 
         CaptureSessionInterface captureSession = mCaptureSession;
         ListenableFuture<Void> openCaptureSession = captureSession.open(validatingBuilder.build(),
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
index d0553ae..a5d14b5 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
@@ -129,6 +129,9 @@
     @SuppressWarnings("WeakerAccess") /* synthetic accessor */
     @GuardedBy("mSessionLock")
     CallbackToFutureAdapter.Completer<Void> mReleaseCompleter;
+    @NonNull
+    @GuardedBy("mSessionLock")
+    Map<DeferrableSurface, Long> mStreamUseCaseMap = new HashMap<>();
     final StillCaptureFlow mStillCaptureFlow = new StillCaptureFlow();
     final TorchStateReset mTorchStateReset = new TorchStateReset();
 
@@ -140,6 +143,13 @@
         mCaptureSessionStateCallback = new StateCallback();
     }
 
+    @Override
+    public void setStreamUseCaseMap(@NonNull Map<DeferrableSurface, Long> streamUseCaseMap) {
+        synchronized (mSessionLock) {
+            mStreamUseCaseMap = streamUseCaseMap;
+        }
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -314,11 +324,9 @@
                                         outputConfig,
                                         mConfiguredSurfaceMap,
                                         physicalCameraIdForAllStreams);
-                        if (sessionConfig.getImplementationOptions().containsOption(
-                                Camera2ImplConfig.STREAM_USE_CASE_OPTION)) {
+                        if (mStreamUseCaseMap.containsKey(outputConfig.getSurface())) {
                             outputConfiguration.setStreamUseCase(
-                                    sessionConfig.getImplementationOptions().retrieveOption(
-                                            Camera2ImplConfig.STREAM_USE_CASE_OPTION));
+                                    mStreamUseCaseMap.get(outputConfig.getSurface()));
                         }
                         outputConfigList.add(outputConfiguration);
                     }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSessionInterface.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSessionInterface.java
index 73fbb81..351c8f5 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSessionInterface.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSessionInterface.java
@@ -24,11 +24,13 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.camera.core.impl.CaptureConfig;
+import androidx.camera.core.impl.DeferrableSurface;
 import androidx.camera.core.impl.SessionConfig;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * An interface for manipulating the session to capture images from the camera which is tied to a
@@ -120,4 +122,13 @@
      */
     @NonNull
     ListenableFuture<Void> release(boolean abortInFlightCaptures);
+
+    /**
+     * Sets the mapping relations between surfaces and the streamUseCases of their associated
+     * streams
+     *
+     * @param streamUseCaseMap the mapping between surfaces and the streamUseCase flag of the
+     *                         associated streams
+     */
+    void setStreamUseCaseMap(@NonNull Map<DeferrableSurface, Long> streamUseCaseMap);
 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
index e530e40..f236af6 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/ProcessingCaptureSession.java
@@ -573,6 +573,11 @@
         }
     }
 
+    @Override
+    public void setStreamUseCaseMap(@NonNull Map<DeferrableSurface, Long> streamUseCaseMap) {
+        // No-op
+    }
+
     private void updateParameters(@NonNull CaptureRequestOptions sessionOptions,
             @NonNull CaptureRequestOptions stillCaptureOptions) {
         Camera2ImplConfig.Builder builder = new Camera2ImplConfig.Builder();
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 7af6200..cb1a181 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
@@ -16,19 +16,28 @@
 
 package androidx.camera.camera2.internal;
 
+import static androidx.camera.camera2.impl.Camera2ImplConfig.STREAM_USE_CASE_OPTION;
+
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraMetadata;
+import android.media.MediaCodec;
 import android.os.Build;
 
 import androidx.annotation.NonNull;
-import androidx.camera.camera2.internal.compat.params.OutputConfigurationCompat;
-import androidx.camera.core.impl.ImageAnalysisConfig;
-import androidx.camera.core.impl.ImageCaptureConfig;
-import androidx.camera.core.impl.PreviewConfig;
+import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
+import androidx.camera.core.ImageAnalysis;
+import androidx.camera.core.ImageCapture;
+import androidx.camera.core.Preview;
+import androidx.camera.core.UseCase;
+import androidx.camera.core.impl.DeferrableSurface;
 import androidx.camera.core.impl.SessionConfig;
-import androidx.camera.core.impl.UseCaseConfig;
 
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * A class that contains utility methods for stream use case.
@@ -36,79 +45,74 @@
 public final class StreamUseCaseUtil {
 
     private StreamUseCaseUtil() {
+
+    }
+
+    private static Map<Class<?>, Long> sUseCaseToStreamUseCaseMapping;
+
+    /**
+     * Populates the mapping between surfaces of a capture session and the Stream Use Case of their
+     * associated stream.
+     *
+     * @param sessionConfigs   collection of all session configs for this capture session
+     * @param streamUseCaseMap the mapping between surfaces and Stream Use Case flag
+     */
+    @OptIn(markerClass = ExperimentalCamera2Interop.class)
+    public static void populateSurfaceToStreamUseCaseMapping(
+            @NonNull Collection<SessionConfig> sessionConfigs,
+            @NonNull Map<DeferrableSurface, Long> streamUseCaseMap) {
+        if (Build.VERSION.SDK_INT < 33) {
+            return;
+        }
+        for (SessionConfig sessionConfig : sessionConfigs) {
+            if (sessionConfig.getTemplateType()
+                    == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG
+            ) {
+                // If is ZSL, do not populate anything.
+                streamUseCaseMap.clear();
+                return;
+            }
+            for (DeferrableSurface surface : sessionConfig.getSurfaces()) {
+                if (sessionConfig.getImplementationOptions().containsOption(STREAM_USE_CASE_OPTION)
+                        &&
+                        sessionConfig.getImplementationOptions()
+                                .retrieveOption(STREAM_USE_CASE_OPTION) != null
+                ) {
+                    streamUseCaseMap.put(
+                            surface,
+                            sessionConfig.getImplementationOptions()
+                                    .retrieveOption(STREAM_USE_CASE_OPTION));
+
+                    continue;
+                }
+
+                @Nullable Long flag = getUseCaseToStreamUseCaseMapping()
+                        .get(surface.getContainerClass());
+                if (flag != null) {
+                    streamUseCaseMap.put(surface, flag);
+                }
+            }
+        }
     }
 
     /**
-     * Returns the appropriate stream use case for a capture session based on the attached
-     * CameraX use cases. If API level is below 33, return
-     * {@value OutputConfigurationCompat#STREAM_USE_CASE_NONE}. If use cases are empty or is ZSL,
-     * return DEFAULT. Otherwise, return PREVIEW_VIDEO_STILL for ImageCapture + VideoCapture;
-     * return STILL_CAPTURE for ImageCapture; return VIDEO_RECORD for VideoCapture; return
-     * VIEW_FINDER for Preview only.
-     *
-     * @param useCaseConfigs collection of all attached CameraX use cases for this capture session
-     * @param sessionConfigs collection of all session configs for this capture session
-     * @return the appropriate stream use case for this capture session
+     * Returns the mapping between the container class of a surface and the StreamUseCase
+     * associated with that class. Refer to {@link UseCase} for the potential UseCase as the
+     * container class for a given surface.
      */
-    public static long getStreamUseCaseFromUseCaseConfigs(
-            @NonNull Collection<UseCaseConfig<?>> useCaseConfigs,
-            @NonNull Collection<SessionConfig> sessionConfigs) {
-        if (Build.VERSION.SDK_INT < 33) {
-            return OutputConfigurationCompat.STREAM_USE_CASE_NONE;
+    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+    private static Map<Class<?>, Long> getUseCaseToStreamUseCaseMapping() {
+        if (sUseCaseToStreamUseCaseMapping == null) {
+            sUseCaseToStreamUseCaseMapping = new HashMap<>();
+            sUseCaseToStreamUseCaseMapping.put(ImageAnalysis.class,
+                    Long.valueOf(CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
+            sUseCaseToStreamUseCaseMapping.put(Preview.class,
+                    Long.valueOf(CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW));
+            sUseCaseToStreamUseCaseMapping.put(ImageCapture.class,
+                    Long.valueOf(CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE));
+            sUseCaseToStreamUseCaseMapping.put(MediaCodec.class,
+                    Long.valueOf(CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD));
         }
-        if (useCaseConfigs.isEmpty()) {
-            //If the collection is empty, return default case.
-            return CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
-        } else {
-            for (SessionConfig sessionConfig : sessionConfigs) {
-                if (sessionConfig.getTemplateType() == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG) {
-                    //If is ZSL, return default case.
-                    return CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
-                }
-            }
-            boolean hasImageCapture = false, hasVideoCapture = false, hasPreview = false;
-            for (UseCaseConfig<?> useCaseConfig : useCaseConfigs) {
-                if (useCaseConfig instanceof ImageAnalysisConfig) {
-                    //If contains analysis use case, return default case.
-                    return CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
-                }
-
-                if (useCaseConfig instanceof PreviewConfig) {
-                    hasPreview = true;
-                    continue;
-                }
-
-                if (useCaseConfig instanceof ImageCaptureConfig) {
-                    if (hasVideoCapture) {
-                        // If has both image and video capture, return preview video still case.
-                        return CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL;
-                    }
-                    hasImageCapture = true;
-                    continue;
-
-                }
-
-                // TODO: Need to handle "hasVideoCapture". The original statement was removed in
-                //  aosp/2299682 because it uses the legacy core.VideoCapture API, which means the
-                //  statement's content will never be run. The new video.VideoCapture API should
-                //  used.
-            }
-
-            if (hasImageCapture) {
-                // If contains image capture, return still capture case.
-                return CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE;
-            } else if (hasVideoCapture) {
-                // If contains video capture, return video record case.
-                return CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD;
-            } else {
-                if (!hasPreview) {
-                    // If doesn't contain preview, we are not sure what's the situation. Return
-                    // default case.
-                    return CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
-                }
-                // If contains only preview, return view finder case.
-                return CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW;
-            }
-        }
+        return sUseCaseToStreamUseCaseMapping;
     }
 }