Add VideoCapture.getSelectedQuality() API

 This API allows retrieving the selected Quality for a VideoCapture instance based on the provided QualitySelector.

 The getSelectedQuality() method returns a value only when the VideoCapture is bound to a lifecycle.

 This API was requested by both JCA and 3rd-party developers.

Relnote: "Add VideoCapture.getSelectedQuality() to know the selected Quality based on the QualitySelector."

Bug: 204288986
Test: VideoCaptureTest
Change-Id: I7050868dea1f1654386c991d441c25af2e3f1fe4
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/StreamSpec.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/StreamSpec.java
index bbd3ff2..2552a60 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/StreamSpec.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/StreamSpec.java
@@ -21,6 +21,8 @@
 import android.util.Size;
 
 import androidx.camera.core.DynamicRange;
+import androidx.camera.core.ViewPort;
+import androidx.camera.core.streamsharing.StreamSharing;
 
 import com.google.auto.value.AutoValue;
 
@@ -46,6 +48,25 @@
     public abstract @NonNull Size getResolution();
 
     /**
+     * Returns the original resolution configured by the camera. This value is useful for
+     * debugging and analysis, as it represents the initial resolution intended for the stream,
+     * even if the stream is later modified.
+     *
+     * <p>This value typically matches the resolution returned by {@link #getResolution()},
+     * but may differ if the stream is modified (e.g., cropped, scaled, or rotated)
+     * after being configured by the camera. For example, {@link StreamSharing} first determines
+     * which child use case's requested resolution to be its configured resolution and then
+     * request a larger resolution from the camera. The camera stream is further modified (e.g.,
+     * cropped, scaled, or rotated) to fit the configured resolution and other requirements such
+     * as {@link ViewPort} and rotation. The final resolution after these
+     * modifications would be reflected by {@link #getResolution()}, while this method returns the
+     * original configured resolution.
+     *
+     * @return The originally configured camera resolution.
+     */
+    public abstract @NonNull Size getOriginalConfiguredResolution();
+
+    /**
      * Returns the {@link DynamicRange} for the stream associated with this stream specification.
      * @return the dynamic range for the stream.
      */
@@ -74,6 +95,7 @@
     public static @NonNull Builder builder(@NonNull Size resolution) {
         return new AutoValue_StreamSpec.Builder()
                 .setResolution(resolution)
+                .setOriginalConfiguredResolution(resolution)
                 .setExpectedFrameRateRange(FRAME_RATE_RANGE_UNSPECIFIED)
                 .setDynamicRange(DynamicRange.SDR)
                 .setZslDisabled(false);
@@ -93,6 +115,25 @@
         public abstract @NonNull Builder setResolution(@NonNull Size resolution);
 
         /**
+         * Sets the original resolution configured by the camera. This value is useful for
+         * debugging and analysis, as it represents the initial resolution intended by the stream
+         * consumer, even if the stream is later modified.
+         *
+         * <p>This value typically matches the resolution set by {@link #setResolution(Size)},
+         * but may differ if the stream is modified (e.g., cropped, scaled, or rotated)
+         * after being configured by the camera. For example, {@link StreamSharing} first
+         * determines which child use case's requested resolution to be its configured resolution
+         * and then request a larger resolution from the camera. The camera stream is further
+         * modified (e.g., cropped, scaled, or rotated) to fit the configured resolution and other
+         * requirements such as {@link ViewPort} and rotation. The final resolution after these
+         * modifications is set by {@link #setResolution(Size)}, while this method retains the
+         * original configured resolution.
+         *
+         * <p>If not set, this value will default to the resolution set in this builder.
+         */
+        public abstract @NonNull Builder setOriginalConfiguredResolution(@NonNull Size resolution);
+
+        /**
          * Sets the dynamic range.
          *
          * <p>If not set, the default dynamic range is {@link DynamicRange#SDR}.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/PreferredChildSize.kt b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/PreferredChildSize.kt
new file mode 100644
index 0000000..1b65043
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/PreferredChildSize.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.streamsharing
+
+import android.graphics.Rect
+import android.util.Size
+
+/** Data class representing the preferred size information for a child. */
+internal data class PreferredChildSize(
+    /** The cropping rectangle to apply before scaling. */
+    val cropRectBeforeScaling: Rect,
+
+    /** The size of the child after scaling. */
+    val childSizeToScale: Size,
+
+    /**
+     * The original selected size from the child's preferred sizes before any scaling, cropping, or
+     * rotating.
+     */
+    val originalSelectedChildSize: Size
+)
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/ResolutionsMerger.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/ResolutionsMerger.java
index 3ad3c04..deef99f 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/ResolutionsMerger.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/ResolutionsMerger.java
@@ -130,10 +130,11 @@
     }
 
     /**
-     * Returns a preferred pair composed of a crop rect before scaling and a size after scaling.
+     * Returns a {@link PreferredChildSize} object containing the preferred size information for a
+     * child.
      *
      * <p>The first size in the child's ordered size list that does not require the parent to
-     * upscale and does not cause double-cropping will be used to generate the pair, or {@code
+     * upscale and does not cause double-cropping will be used to generate the result, or {@code
      * parentCropRect} will be used if no matching is found.
      *
      * <p>The returned crop rect and size will have the same aspect-ratio. When {@code
@@ -143,8 +144,10 @@
      * <p>Notes that the input {@code childConfig} is expected to be one of the values that use to
      * construct the {@link ResolutionsMerger}, if not an IllegalArgumentException will be thrown.
      */
-    @NonNull Pair<Rect, Size> getPreferredChildSizePair(@NonNull UseCaseConfig<?> childConfig,
-            @NonNull Rect parentCropRect, int sensorToBufferRotationDegrees,
+    @NonNull PreferredChildSize getPreferredChildSize(
+            @NonNull UseCaseConfig<?> childConfig,
+            @NonNull Rect parentCropRect,
+            int sensorToBufferRotationDegrees,
             boolean isViewportSet) {
         // For easier in following computations, width and height are reverted when the rotation
         // degrees of sensor-to-buffer is 90 or 270.
@@ -154,27 +157,27 @@
             isWidthHeightRevertedForComputation = true;
         }
 
-        // Get preferred child size pair.
-        Pair<Rect, Size> pair = getPreferredChildSizePairInternal(parentCropRect, childConfig,
-                isViewportSet);
-        Rect cropRectBeforeScaling = pair.first;
-        Size childSizeToScale = pair.second;
+        // Get preferred child size.
+        PreferredChildSize preferredChildSize = getPreferredChildSizeInternal(
+                parentCropRect, childConfig, isViewportSet);
 
         // Restore the reversion of width and height
         if (isWidthHeightRevertedForComputation) {
-            childSizeToScale = reverseSize(childSizeToScale);
-            cropRectBeforeScaling = reverseRect(cropRectBeforeScaling);
+            preferredChildSize = new PreferredChildSize(
+                    reverseRect(preferredChildSize.getCropRectBeforeScaling()),
+                    reverseSize(preferredChildSize.getChildSizeToScale()),
+                    preferredChildSize.getOriginalSelectedChildSize());
         }
 
-        return new Pair<>(cropRectBeforeScaling, childSizeToScale);
-
+        return preferredChildSize;
     }
 
-    private @NonNull Pair<Rect, Size> getPreferredChildSizePairInternal(
+    private @NonNull PreferredChildSize getPreferredChildSizeInternal(
             @NonNull Rect parentCropRect, @NonNull UseCaseConfig<?> childConfig,
             boolean isViewportSet) {
         Rect cropRectBeforeScaling;
         Size childSizeToScale;
+        Size selectedChildSize;
 
         if (isViewportSet) {
             cropRectBeforeScaling = parentCropRect;
@@ -182,14 +185,16 @@
             // When viewport is set, child size needs to be cropped to match viewport's
             // aspect-ratio.
             Size viewPortSize = rectToSize(parentCropRect);
-            childSizeToScale = getPreferredChildSizeForViewport(viewPortSize, childConfig);
+            Pair<Size, Size> pair = getPreferredChildSizeForViewport(viewPortSize, childConfig);
+            selectedChildSize = pair.first;
+            childSizeToScale = pair.second;
         } else {
             Size parentSize = rectToSize(parentCropRect);
-            childSizeToScale = getPreferredChildSize(parentSize, childConfig);
+            childSizeToScale = selectedChildSize = getPreferredChildSize(parentSize, childConfig);
             cropRectBeforeScaling = getCropRectOfReferenceAspectRatio(parentSize, childSizeToScale);
         }
 
-        return new Pair<>(cropRectBeforeScaling, childSizeToScale);
+        return new PreferredChildSize(cropRectBeforeScaling, childSizeToScale, selectedChildSize);
     }
 
     /**
@@ -242,7 +247,7 @@
      * construct the {@link ResolutionsMerger}, if not an IllegalArgumentException will be thrown.
      */
     @VisibleForTesting
-    @NonNull Size getPreferredChildSizeForViewport(@NonNull Size parentSize,
+    @NonNull Pair<Size, Size> getPreferredChildSizeForViewport(@NonNull Size parentSize,
             @NonNull UseCaseConfig<?> childConfig) {
         List<Size> candidateChildSizes = getSortedChildSizes(childConfig);
 
@@ -251,11 +256,11 @@
                     getCropRectOfReferenceAspectRatio(childSize, parentSize));
 
             if (!hasUpscaling(childSizeToCrop, parentSize)) {
-                return childSizeToCrop;
+                return Pair.create(childSize, childSizeToCrop);
             }
         }
 
-        return parentSize;
+        return Pair.create(parentSize, parentSize);
     }
 
     private @NonNull List<Size> getCameraSupportedResolutions() {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
index ec4402b..565ee32 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
@@ -287,7 +287,10 @@
                 outputEdges.put(entry.getKey(), out.get(entry.getValue()));
             }
 
-            mVirtualCameraAdapter.setChildrenEdges(outputEdges);
+            Map<UseCase, Size> selectedChildSizeMap = mVirtualCameraAdapter.getSelectedChildSizes(
+                    mSharingInputEdge, isViewportSet);
+
+            mVirtualCameraAdapter.setChildrenEdges(outputEdges, selectedChildSizeMap);
 
             return List.of(mSessionConfigBuilder.build());
         } else {
@@ -323,7 +326,11 @@
             for (Map.Entry<UseCase, DualOutConfig> entry : outConfigMap.entrySet()) {
                 outputEdges.put(entry.getKey(), out.get(entry.getValue()));
             }
-            mVirtualCameraAdapter.setChildrenEdges(outputEdges);
+
+            Map<UseCase, Size> primarySelectedChildSizes =
+                    mVirtualCameraAdapter.getSelectedChildSizes(mSharingInputEdge, isViewportSet);
+
+            mVirtualCameraAdapter.setChildrenEdges(outputEdges, primarySelectedChildSizes);
 
             return List.of(mSessionConfigBuilder.build(),
                     mSecondarySessionConfigBuilder.build());
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraAdapter.java
index 3121dbd..d903782 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/VirtualCameraAdapter.java
@@ -37,7 +37,6 @@
 
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
-import android.util.Pair;
 import android.util.Range;
 import android.util.Size;
 import android.view.Surface;
@@ -222,7 +221,8 @@
     /**
      * Gets {@link OutConfig} for children {@link UseCase} based on the input edge.
      */
-    @NonNull Map<UseCase, OutConfig> getChildrenOutConfigs(@NonNull SurfaceEdge sharingInputEdge,
+    @NonNull Map<UseCase, OutConfig> getChildrenOutConfigs(
+            @NonNull SurfaceEdge sharingInputEdge,
             @ImageOutputConfig.RotationValue int parentTargetRotation, boolean isViewportSet) {
         Map<UseCase, OutConfig> outConfigs = new HashMap<>();
         for (UseCase useCase : mChildren) {
@@ -233,6 +233,24 @@
         return outConfigs;
     }
 
+    /**
+     * Gets original selected size for children {@link UseCase} based on the input edge.
+     */
+    @NonNull Map<UseCase, Size> getSelectedChildSizes(@NonNull SurfaceEdge sharingInputEdge,
+            boolean isViewportSet) {
+        Map<UseCase, Size> selectedChildSizes = new HashMap<>();
+        for (UseCase useCase : mChildren) {
+            PreferredChildSize preferredChildSize = mResolutionsMerger
+                    .getPreferredChildSize(
+                            requireNonNull(mChildrenConfigsMap.get(useCase)),
+                            sharingInputEdge.getCropRect(),
+                            getRotationDegrees(sharingInputEdge.getSensorToBufferTransform()),
+                            isViewportSet);
+            selectedChildSizes.put(useCase, preferredChildSize.getOriginalSelectedChildSize());
+        }
+        return selectedChildSizes;
+    }
+
     @NonNull Map<UseCase, DualOutConfig> getChildrenOutConfigs(
             @NonNull SurfaceEdge primaryInputEdge,
             @NonNull SurfaceEdge secondaryInputEdge,
@@ -247,7 +265,7 @@
                     parentTargetRotation, isViewportSet);
             // secondary
             OutConfig secondaryOutConfig = calculateOutConfig(
-                    useCase, mSecondaryResolutionsMerger,
+                    useCase, requireNonNull(mSecondaryResolutionsMerger),
                     requireNonNull(mSecondaryParentCamera),
                     secondaryInputEdge,
                     parentTargetRotation, isViewportSet);
@@ -270,14 +288,14 @@
                 .getSensorRotationDegrees(parentTargetRotation);
         boolean parentIsMirrored = isMirrored(
                 cameraInputEdge.getSensorToBufferTransform());
-        Pair<Rect, Size> preferredSizePair = resolutionsMerger
-                .getPreferredChildSizePair(
+        PreferredChildSize preferredChildSize = resolutionsMerger
+                .getPreferredChildSize(
                         requireNonNull(mChildrenConfigsMap.get(useCase)),
                         cameraInputEdge.getCropRect(),
                         getRotationDegrees(cameraInputEdge.getSensorToBufferTransform()),
                         isViewportSet);
-        Rect cropRectBeforeScaling = preferredSizePair.first;
-        Size childSizeToScale = preferredSizePair.second;
+        Rect cropRectBeforeScaling = preferredChildSize.getCropRectBeforeScaling();
+        Size childSizeToScale = preferredChildSize.getChildSizeToScale();
 
         // Only use primary camera info for output surface
         int childRotationDegrees = getChildRotationDegrees(useCase, mParentCamera);
@@ -299,7 +317,8 @@
     /**
      * Update children {@link SurfaceEdge} calculated by {@link StreamSharing}.
      */
-    void setChildrenEdges(@NonNull Map<UseCase, SurfaceEdge> childrenEdges) {
+    void setChildrenEdges(@NonNull Map<UseCase, SurfaceEdge> childrenEdges,
+            @NonNull Map<UseCase, @NonNull Size> selectedChildSizes) {
         mChildrenEdges.clear();
         mChildrenEdges.putAll(childrenEdges);
         for (Map.Entry<UseCase, SurfaceEdge> entry : mChildrenEdges.entrySet()) {
@@ -307,7 +326,9 @@
             SurfaceEdge surfaceEdge = entry.getValue();
             useCase.setViewPortCropRect(surfaceEdge.getCropRect());
             useCase.setSensorToBufferTransformMatrix(surfaceEdge.getSensorToBufferTransform());
-            useCase.updateSuggestedStreamSpec(surfaceEdge.getStreamSpec(), null);
+            StreamSpec streamSpec = getChildStreamSpec(useCase, surfaceEdge.getStreamSpec(),
+                    selectedChildSizes);
+            useCase.updateSuggestedStreamSpec(streamSpec, null);
             useCase.notifyState();
         }
     }
@@ -401,6 +422,17 @@
         return cameraInternal.getCameraInfo().getSensorRotationDegrees(childTargetRotation);
     }
 
+    @NonNull
+    private static StreamSpec getChildStreamSpec(@NonNull UseCase useCase,
+            @NonNull StreamSpec baseStreamSpec, @NonNull Map<UseCase, Size> selectedChildSizes) {
+        StreamSpec.Builder builder = baseStreamSpec.toBuilder();
+        Size selectedChildSize = selectedChildSizes.get(useCase);
+        if (selectedChildSize != null) {
+            builder.setOriginalConfiguredResolution(selectedChildSize);
+        }
+        return builder.build();
+    }
+
     private static int getChildFormat(@NonNull UseCase useCase) {
         return useCase instanceof ImageCapture ? ImageFormat.JPEG
                 : INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE;
@@ -517,8 +549,8 @@
         Range<Integer> resolvedTargetFrameRate = StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED;
 
         for (UseCaseConfig<?> useCaseConfig : useCaseConfigs) {
-            Range<Integer> targetFrameRate = useCaseConfig.getTargetFrameRate(
-                    resolvedTargetFrameRate);
+            Range<Integer> targetFrameRate = requireNonNull(useCaseConfig.getTargetFrameRate(
+                    resolvedTargetFrameRate));
 
             if (StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED.equals(resolvedTargetFrameRate)) {
                 resolvedTargetFrameRate = targetFrameRate;
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/ResolutionsMergerTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/ResolutionsMergerTest.kt
index e7bbbd3..0a227b2 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/ResolutionsMergerTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/ResolutionsMergerTest.kt
@@ -18,6 +18,7 @@
 
 import android.graphics.Rect
 import android.os.Build
+import android.util.Pair
 import android.util.Rational
 import android.util.Size
 import androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
@@ -337,7 +338,7 @@
     }
 
     @Test(expected = IllegalArgumentException::class)
-    fun getPreferredChildSizePair_whenConfigNotPassedToConstructor_throwsException() {
+    fun getPreferredChildSize_whenUseCaseConfigNotPassedToConstructor_throwsException() {
         // Arrange.
         val config = createUseCaseConfig()
         val sorter = FakeSupportedOutputSizesSorter(mapOf(config to SIZES_16_9))
@@ -345,11 +346,11 @@
 
         // Act.
         val useCaseConfigNotPassed = createUseCaseConfig()
-        merger.getPreferredChildSizePair(useCaseConfigNotPassed, SIZE_1920_1440.toRect(), 0, false)
+        merger.getPreferredChildSize(useCaseConfigNotPassed, SIZE_1920_1440.toRect(), 0, false)
     }
 
     @Test
-    fun getPreferredChildSizePair_whenViewportIsNotSet_canReturnCorrectly() {
+    fun getPreferredChildSize_whenViewportIsNotSet_canReturnCorrectly() {
         // Arrange.
         val config = createUseCaseConfig()
         val candidateChildSizes =
@@ -367,21 +368,18 @@
 
         // Act & Assert, should returns the first child size that do not need upscale and cause
         // double-cropping.
-        merger
-            .getPreferredChildSizePair(config, SIZE_2560_1440.toRect(), 0, false)
-            .containsExactly(SIZE_2560_1440.toRect(), SIZE_1920_1080)
-        merger
-            .getPreferredChildSizePair(config, SIZE_1280_720.toRect(), 0, false)
-            .containsExactly(SIZE_1280_720.toRect(), SIZE_960_540)
+        assertThat(merger.getPreferredChildSize(config, SIZE_2560_1440.toRect(), 0, false))
+            .isEqualTo(PreferredChildSize(SIZE_2560_1440.toRect(), SIZE_1920_1080, SIZE_1920_1080))
+        assertThat(merger.getPreferredChildSize(config, SIZE_1280_720.toRect(), 0, false))
+            .isEqualTo(PreferredChildSize(SIZE_1280_720.toRect(), SIZE_960_540, SIZE_960_540))
 
         // Act & Assert, should returns parent size when no matching.
-        merger
-            .getPreferredChildSizePair(config, SIZE_192_108.toRect(), 0, false)
-            .containsExactly(SIZE_192_108.toRect(), SIZE_192_108)
+        assertThat(merger.getPreferredChildSize(config, SIZE_192_108.toRect(), 0, false))
+            .isEqualTo(PreferredChildSize(SIZE_192_108.toRect(), SIZE_192_108, SIZE_192_108))
     }
 
     @Test
-    fun getPreferredChildSizePair_whenViewportIsSet_canReturnCorrectly() {
+    fun getPreferredChildSize_whenViewportIsSet_canReturnCorrectly() {
         // Arrange.
         val config = createUseCaseConfig()
         val candidateChildSizes =
@@ -396,25 +394,22 @@
         // Act & Assert, should returns 1:1 crop rect and size, that are generated from the first
         // child size that do not need upscale.
         val rect1440To1440 = SIZE_2560_1920.crop(Size(1440, 1440))
-        merger
-            .getPreferredChildSizePair(config, rect1440To1440, 0, true)
-            .containsExactly(rect1440To1440, Size(1080, 1080))
+        assertThat(merger.getPreferredChildSize(config, rect1440To1440, 0, true))
+            .isEqualTo(PreferredChildSize(rect1440To1440, Size(1080, 1080), SIZE_1920_1080))
         val rect720To720 = SIZE_1280_720.crop(Size(720, 720))
-        merger
-            .getPreferredChildSizePair(config, rect720To720, 0, true)
-            .containsExactly(rect720To720, Size(540, 540))
+        assertThat(merger.getPreferredChildSize(config, rect720To720, 0, true))
+            .isEqualTo(PreferredChildSize(rect720To720, Size(540, 540), SIZE_960_540))
 
         // Act & Assert, should returns crop rect and size, that are generated from parent size
         // when no matching.
         val size108To108 = Size(108, 108)
         val rect108To108 = SIZE_192_108.crop(size108To108)
-        merger
-            .getPreferredChildSizePair(config, rect108To108, 0, true)
-            .containsExactly(rect108To108, size108To108)
+        assertThat(merger.getPreferredChildSize(config, rect108To108, 0, true))
+            .isEqualTo(PreferredChildSize(rect108To108, size108To108, size108To108))
     }
 
     @Test
-    fun getPreferredChildSizePair_whenViewportIsSetAndRotationIs90_canReturnCorrectly() {
+    fun getPreferredChildSize_whenViewportIsSetAndRotationIs90_canReturnCorrectly() {
         // Arrange.
         val config = createUseCaseConfig()
         val candidateChildSizes =
@@ -429,20 +424,18 @@
         // Act & Assert, should returns 1:2 crop rect and size, that are generated from the first
         // child size that do not need upscale.
         val rect1280To2560 = SIZE_2560_1440.crop(Size(2560, 1280)).reverse()
-        merger
-            .getPreferredChildSizePair(config, rect1280To2560, 90, true)
-            .containsExactly(rect1280To2560, Size(960, 1920))
+        assertThat(merger.getPreferredChildSize(config, rect1280To2560, 90, true))
+            .isEqualTo(PreferredChildSize(rect1280To2560, Size(960, 1920), SIZE_1920_1080))
         val rect640To1280 = SIZE_1280_720.crop(Size(1280, 640)).reverse()
-        merger
-            .getPreferredChildSizePair(config, rect640To1280, 90, true)
-            .containsExactly(rect640To1280, Size(480, 960))
+        assertThat(merger.getPreferredChildSize(config, rect640To1280, 90, true))
+            .isEqualTo(PreferredChildSize(rect640To1280, Size(480, 960), SIZE_960_540))
 
         // Act & Assert, should returns crop rect and size, that are generated from parent size
         // when no matching.
-        val rect96To192 = SIZE_192_108.crop(Size(192, 96)).reverse()
-        merger
-            .getPreferredChildSizePair(config, rect96To192, 90, true)
-            .containsExactly(rect96To192, rectToSize(rect96To192))
+        val size192To96 = Size(192, 96)
+        val rect96To192 = SIZE_192_108.crop(size192To96).reverse()
+        assertThat(merger.getPreferredChildSize(config, rect96To192, 90, true))
+            .isEqualTo(PreferredChildSize(rect96To192, rectToSize(rect96To192), size192To96))
     }
 
     @Test(expected = IllegalArgumentException::class)
@@ -531,13 +524,13 @@
         // Act & Assert, should returns the first child size that can be cropped to parent
         // aspect-ratio and do not cause upscaling.
         assertThat(merger.getPreferredChildSizeForViewport(SIZE_2560_1920, config))
-            .isEqualTo(SIZE_1920_1440)
+            .isEqualTo(Pair.create(SIZE_1920_1440, SIZE_1920_1440))
         assertThat(merger.getPreferredChildSizeForViewport(SIZE_1280_960, config))
-            .isEqualTo(SIZE_960_720)
+            .isEqualTo(Pair.create(SIZE_960_720, SIZE_960_720))
 
         // Act & Assert, should returns parent size when no matching.
         assertThat(merger.getPreferredChildSizeForViewport(SIZE_640_480, config))
-            .isEqualTo(SIZE_640_480)
+            .isEqualTo(Pair.create(SIZE_640_480, SIZE_640_480))
     }
 
     @Test
@@ -556,13 +549,13 @@
         // Act & Assert, should returns the first child size that can be cropped to parent
         // aspect-ratio and do not cause upscaling.
         assertThat(merger.getPreferredChildSizeForViewport(SIZE_1920_1440, config))
-            .isEqualTo(Size(1440, 1080))
+            .isEqualTo(Pair.create(SIZE_1920_1080, Size(1440, 1080)))
         assertThat(merger.getPreferredChildSizeForViewport(SIZE_1280_960, config))
-            .isEqualTo(SIZE_960_720)
+            .isEqualTo(Pair.create(SIZE_1280_720, SIZE_960_720))
 
         // Act & Assert, should returns parent size when no matching.
         assertThat(merger.getPreferredChildSizeForViewport(SIZE_640_480, config))
-            .isEqualTo(SIZE_640_480)
+            .isEqualTo(Pair.create(SIZE_640_480, SIZE_640_480))
     }
 
     @Test
@@ -776,11 +769,6 @@
         return FakeUseCaseConfig.Builder().useCaseConfig
     }
 
-    private fun android.util.Pair<Rect, Size>.containsExactly(rect: Rect, size: Size) {
-        assertThat(first).isEqualTo(rect)
-        assertThat(second).isEqualTo(size)
-    }
-
     private fun Rect.hasMatchingAspectRatio(resolution: Size): Boolean {
         return AspectRatioUtil.hasMatchingAspectRatio(resolution, Rational(width(), height()))
     }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraAdapterTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraAdapterTest.kt
index 2d8f0f8..1049990 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraAdapterTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/streamsharing/VirtualCameraAdapterTest.kt
@@ -97,6 +97,8 @@
             Pair(child1 as UseCase, createSurfaceEdge()),
             Pair(child2 as UseCase, createSurfaceEdge())
         )
+    private val selectedChildSizes =
+        mapOf<UseCase, Size>(child1 to INPUT_SIZE, child2 to INPUT_SIZE)
     private val useCaseConfigFactory = FakeUseCaseConfigFactory()
     private lateinit var adapter: VirtualCameraAdapter
     private var snapshotTriggered = false
@@ -223,7 +225,7 @@
     fun setUseCaseActiveAndInactive_surfaceConnectsAndDisconnects() {
         // Arrange.
         adapter.bindChildren()
-        adapter.setChildrenEdges(childrenEdges)
+        adapter.setChildrenEdges(childrenEdges, selectedChildSizes)
         child1.updateSessionConfigForTesting(SESSION_CONFIG_WITH_SURFACE)
         // Assert: edge open by default.
         verifyEdge(child1, OPEN, NO_PROVIDER)
@@ -242,7 +244,7 @@
     fun resetWithClosedChildSurface_invokesErrorListener() {
         // Arrange.
         adapter.bindChildren()
-        adapter.setChildrenEdges(childrenEdges)
+        adapter.setChildrenEdges(childrenEdges, selectedChildSizes)
         child1.updateSessionConfigForTesting(SESSION_CONFIG_WITH_SURFACE)
         child1.notifyActiveForTesting()
 
@@ -260,7 +262,7 @@
     fun resetUseCase_edgeInvalidated() {
         // Arrange: setup and get the old DeferrableSurface.
         adapter.bindChildren()
-        adapter.setChildrenEdges(childrenEdges)
+        adapter.setChildrenEdges(childrenEdges, selectedChildSizes)
         child1.updateSessionConfigForTesting(SESSION_CONFIG_WITH_SURFACE)
         child1.notifyActiveForTesting()
         val oldSurface = childrenEdges[child1]!!.deferrableSurfaceForTesting
@@ -277,7 +279,7 @@
     fun updateUseCaseWithAndWithoutSurface_surfaceConnectsAndDisconnects() {
         // Arrange
         adapter.bindChildren()
-        adapter.setChildrenEdges(childrenEdges)
+        adapter.setChildrenEdges(childrenEdges, selectedChildSizes)
         child1.notifyActiveForTesting()
         verifyEdge(child1, OPEN, NO_PROVIDER)
 
@@ -364,10 +366,17 @@
     @Test
     fun updateChildrenSpec_updateAndNotifyChildren() {
         // Act: update children with the map.
-        adapter.setChildrenEdges(childrenEdges)
-        // Assert: surface size, crop rect and transformation propagated to children
+        val selectedChildSizes =
+            mapOf<UseCase, Size>(child1 to Size(400, 300), child2 to Size(720, 480))
+        adapter.setChildrenEdges(childrenEdges, selectedChildSizes)
+        // Assert: surface size, original selected size, crop rect and transformation propagated
+        // to children
         assertThat(child1.attachedStreamSpec!!.resolution).isEqualTo(INPUT_SIZE)
+        assertThat(child1.attachedStreamSpec!!.originalConfiguredResolution)
+            .isEqualTo(Size(400, 300))
         assertThat(child2.attachedStreamSpec!!.resolution).isEqualTo(INPUT_SIZE)
+        assertThat(child2.attachedStreamSpec!!.originalConfiguredResolution)
+            .isEqualTo(Size(720, 480))
         assertThat(child1.viewPortCropRect).isEqualTo(CROP_RECT)
         assertThat(child2.viewPortCropRect).isEqualTo(CROP_RECT)
         assertThat(child1.sensorToBufferTransformMatrix).isEqualTo(SENSOR_TO_BUFFER)
diff --git a/camera/camera-video/api/current.txt b/camera/camera-video/api/current.txt
index 0e6ea1c..0c6fcf2 100644
--- a/camera/camera-video/api/current.txt
+++ b/camera/camera-video/api/current.txt
@@ -162,6 +162,7 @@
     method public int getMirrorMode();
     method public T getOutput();
     method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
+    method public androidx.camera.video.Quality? getSelectedQuality();
     method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
     method public int getTargetRotation();
     method public boolean isVideoStabilizationEnabled();
diff --git a/camera/camera-video/api/restricted_current.txt b/camera/camera-video/api/restricted_current.txt
index 0e6ea1c..0c6fcf2 100644
--- a/camera/camera-video/api/restricted_current.txt
+++ b/camera/camera-video/api/restricted_current.txt
@@ -162,6 +162,7 @@
     method public int getMirrorMode();
     method public T getOutput();
     method public androidx.camera.core.ResolutionInfo? getResolutionInfo();
+    method public androidx.camera.video.Quality? getSelectedQuality();
     method public android.util.Range<java.lang.Integer!> getTargetFrameRate();
     method public int getTargetRotation();
     method public boolean isVideoStabilizationEnabled();
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index 43e805a..b947810 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
@@ -56,6 +56,7 @@
 import static androidx.camera.video.internal.utils.DynamicRangeUtil.videoProfileHdrFormatsToDynamicRangeEncoding;
 import static androidx.core.util.Preconditions.checkState;
 
+import static java.util.Collections.emptyMap;
 import static java.util.Collections.singletonList;
 import static java.util.Objects.requireNonNull;
 
@@ -145,6 +146,7 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -195,6 +197,7 @@
     private boolean mHasCompensatingTransformation = false;
     private @Nullable SourceStreamRequirementObserver mSourceStreamRequirementObserver;
     private SessionConfig.@Nullable CloseableErrorListener mCloseableErrorListener;
+    private Map<Quality, List<Size>> mQualityToCustomSizesMap = emptyMap();
 
     /**
      * Create a VideoCapture associated with the given {@link VideoOutput}.
@@ -338,6 +341,44 @@
         return getResolutionInfoInternal();
     }
 
+    /**
+     * Returns the selected Quality.
+     *
+     * <p>The selected Quality represents the final quality level chosen for the stream. The
+     * selected Quality will be one of the specified qualities from the {@link QualitySelector}
+     * provided by the associated {@link VideoOutput}. If {@link Quality#HIGHEST} or
+     * {@link Quality#LOWEST} is specified in the selector, it will be resolved to an actual
+     * Quality value. Even if the stream is later cropped (e.g., by using a {@link ViewPort}), this
+     * value represents the original quality level of the stream.
+     *
+     * <p>This method will return the selected Quality only after the use case is bound using
+     * {@link androidx.camera.lifecycle.ProcessCameraProvider#bindToLifecycle}. Otherwise, it
+     * will return null. The selected Quality may change if the use case is unbound and then
+     * rebound.
+     *
+     * @return The selected Quality if the use case is bound, or null otherwise.
+     */
+    public @Nullable Quality getSelectedQuality() {
+        StreamSpec streamSpec = getAttachedStreamSpec();
+        if (streamSpec == null) {
+            return null;
+        }
+        // In the general case, there should be an exact match from configured resolution to
+        // Quality.
+        Size configuredResolution = streamSpec.getOriginalConfiguredResolution();
+        for (Map.Entry<Quality, List<Size>> entry : mQualityToCustomSizesMap.entrySet()) {
+            if (entry.getValue().contains(configuredResolution)) {
+                return entry.getKey(); // Found exact match, no need to check further
+            }
+        }
+        Logger.w(TAG, "Can't find matched Quality for " + configuredResolution);
+
+        // Fallback to find the nearest available quality. This can occur when StreamSharing
+        // is unable to downscale/crop the camera stream according to the UseCase's preferred
+        // resolution and instead returns the original camera stream resolution.
+        return findNearestSizeFor(mQualityToCustomSizesMap, configuredResolution);
+    }
+
     @RestrictTo(Scope.LIBRARY_GROUP)
     @Override
     protected @Nullable ResolutionInfo getResolutionInfoInternal() {
@@ -1456,64 +1497,83 @@
                 requestedDynamicRange);
         QualityRatioToResolutionsTable qualityRatioTable = new QualityRatioToResolutionsTable(
                 cameraInfo.getSupportedResolutions(getImageFormat()), supportedQualityToSizeMap);
-        List<Size> customOrderedResolutions = new ArrayList<>();
+        // Use LinkedHashMap to maintain the order.
+        LinkedHashMap<Quality, List<Size>> orderedQualityToSizesMap = new LinkedHashMap<>();
         for (Quality selectedQuality : selectedQualities) {
-            customOrderedResolutions.addAll(
+            orderedQualityToSizesMap.put(selectedQuality,
                     qualityRatioTable.getResolutions(selectedQuality, aspectRatio));
         }
-        List<Size> filteredCustomOrderedResolutions = filterOutEncoderUnsupportedResolutions(
-                (VideoCaptureConfig<T>) builder.getUseCaseConfig(), mediaSpec,
-                requestedDynamicRange, videoCapabilities, customOrderedResolutions,
-                supportedQualityToSizeMap);
+        LinkedHashMap<Quality, List<Size>> filteredOrderedQualityToSizesMap =
+                filterOutEncoderUnsupportedResolutions(
+                        (VideoCaptureConfig<T>) builder.getUseCaseConfig(), mediaSpec,
+                        requestedDynamicRange, videoCapabilities, orderedQualityToSizesMap,
+                        supportedQualityToSizeMap);
+        List<Size> filteredCustomOrderedResolutions = new ArrayList<>();
+        for (List<Size> resolutions : filteredOrderedQualityToSizesMap.values()) {
+            filteredCustomOrderedResolutions.addAll(resolutions);
+        }
         Logger.d(TAG, "Set custom ordered resolutions = " + filteredCustomOrderedResolutions);
         builder.getMutableConfig().insertOption(OPTION_CUSTOM_ORDERED_RESOLUTIONS,
                 filteredCustomOrderedResolutions);
+        mQualityToCustomSizesMap = filteredOrderedQualityToSizesMap;
     }
 
-    private static @NonNull List<Size> filterOutEncoderUnsupportedResolutions(
+    private static @NonNull LinkedHashMap<Quality, List<Size>>
+            filterOutEncoderUnsupportedResolutions(
             @NonNull VideoCaptureConfig<?> config,
             @NonNull MediaSpec mediaSpec,
             @NonNull DynamicRange dynamicRange,
             @NonNull VideoCapabilities videoCapabilities,
-            @NonNull List<Size> resolutions,
+            @NonNull LinkedHashMap<Quality, List<Size>> qualityToSizesOrderedMap,
             @NonNull Map<Quality, Size> supportedQualityToSizeMap
     ) {
-        if (resolutions.isEmpty()) {
-            return resolutions;
+        if (qualityToSizesOrderedMap.isEmpty()) {
+            return new LinkedHashMap<>();
         }
 
-        Iterator<Size> iterator = resolutions.iterator();
-        while (iterator.hasNext()) {
-            Size resolution = iterator.next();
-            // To improve performance, there is no need to check for supported qualities'
-            // resolutions because the encoder should support them.
-            if (supportedQualityToSizeMap.containsValue(resolution)) {
-                continue;
+        LinkedHashMap<Quality, List<Size>> filteredQualityToSizesOrderedMap = new LinkedHashMap<>();
+        for (Map.Entry<Quality, List<Size>> entry : qualityToSizesOrderedMap.entrySet()) {
+            // Copy the size list first and filter out the unsupported resolutions.
+            List<Size> filteredSizeList = new ArrayList<>(entry.getValue());
+            Iterator<Size> sizeIterator = filteredSizeList.iterator();
+            while (sizeIterator.hasNext()) {
+                Size resolution = sizeIterator.next();
+                // To improve performance, there is no need to check for supported qualities'
+                // resolutions because the encoder should support them.
+                if (supportedQualityToSizeMap.containsValue(resolution)) {
+                    continue;
+                }
+                // We must find EncoderProfiles for each resolution because the EncoderProfiles
+                // found by resolution may contain different video mine type which leads to
+                // different codec.
+                VideoValidatedEncoderProfilesProxy encoderProfiles =
+                        videoCapabilities.findNearestHigherSupportedEncoderProfilesFor(resolution,
+                                dynamicRange);
+                if (encoderProfiles == null) {
+                    continue;
+                }
+                // If the user set a non-fully specified target DynamicRange, there could be
+                // multiple videoProfiles that matches to the DynamicRange. Find the one with the
+                // largest supported size as a workaround.
+                // If the suggested StreamSpec(i.e. DynamicRange + resolution) is unfortunately over
+                // codec supported size, then rely on surface processing (OpenGL) to resize the
+                // camera stream.
+                VideoEncoderInfo videoEncoderInfo = findLargestSupportedSizeVideoEncoderInfo(
+                        config.getVideoEncoderInfoFinder(), encoderProfiles, dynamicRange,
+                        mediaSpec, resolution,
+                        requireNonNull(config.getTargetFrameRate(Defaults.DEFAULT_FPS_RANGE)));
+                if (videoEncoderInfo != null && !videoEncoderInfo.isSizeSupportedAllowSwapping(
+                        resolution.getWidth(), resolution.getHeight())) {
+                    sizeIterator.remove();
+                }
             }
-            // We must find EncoderProfiles for each resolution because the EncoderProfiles found
-            // by resolution may contain different video mine type which leads to different codec.
-            VideoValidatedEncoderProfilesProxy encoderProfiles =
-                    videoCapabilities.findNearestHigherSupportedEncoderProfilesFor(resolution,
-                            dynamicRange);
-            if (encoderProfiles == null) {
-                continue;
-            }
-            // If the user set a non-fully specified target DynamicRange, there could be multiple
-            // videoProfiles that matches to the DynamicRange. Find the one with the largest
-            // supported size as a workaround.
-            // If the suggested StreamSpec(i.e. DynamicRange + resolution) is unfortunately over
-            // codec supported size, then rely on surface processing (OpenGL) to resize the
-            // camera stream.
-            VideoEncoderInfo videoEncoderInfo = findLargestSupportedSizeVideoEncoderInfo(
-                    config.getVideoEncoderInfoFinder(), encoderProfiles, dynamicRange,
-                    mediaSpec, resolution,
-                    requireNonNull(config.getTargetFrameRate(Defaults.DEFAULT_FPS_RANGE)));
-            if (videoEncoderInfo != null && !videoEncoderInfo.isSizeSupportedAllowSwapping(
-                    resolution.getWidth(), resolution.getHeight())) {
-                iterator.remove();
+
+            // Put the filtered size list only when it is not empty.
+            if (!filteredSizeList.isEmpty()) {
+                filteredQualityToSizesOrderedMap.put(entry.getKey(), filteredSizeList);
             }
         }
-        return resolutions;
+        return filteredQualityToSizesOrderedMap;
     }
 
     private static @Nullable VideoEncoderInfo findLargestSupportedSizeVideoEncoderInfo(
@@ -1556,6 +1616,32 @@
     }
 
     /**
+     * Finds the Quality with the size closest to the target size based on area.
+     *
+     * @param sizeMap The map of Quality to a list of Size`s.
+     * @param targetSize The target size to compare against.
+     * @return The Quality with the closest size, or `null` if no match is found.
+     */
+    private static @Nullable Quality findNearestSizeFor(
+            @NonNull Map<Quality, List<Size>> sizeMap, @NonNull Size targetSize) {
+        int targetArea = getArea(targetSize);
+        Quality nearestQuality = null;
+        int minAreaDiff = Integer.MAX_VALUE;
+
+        for (Map.Entry<Quality, List<Size>> entry : sizeMap.entrySet()) {
+            for (Size size : entry.getValue()) {
+                int areaDiff = Math.abs(getArea(size) - targetArea);
+                if (areaDiff < minAreaDiff) {
+                    minAreaDiff = areaDiff;
+                    nearestQuality = entry.getKey();
+                }
+            }
+        }
+
+        return nearestQuality;
+    }
+
+    /**
      * Gets the snapshot value of the given {@link Observable}.
      *
      * <p>Note: Set {@code valueIfMissing} to a non-{@code null} value doesn't mean the method
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
index 4641453..f0387ef 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
@@ -1847,6 +1847,74 @@
         )
     }
 
+    @Test
+    fun verifySelectedQuality_matchConfiguredResolution() {
+        testSelectedQualityIsExpected(
+            streamSpecConfiguredResolution = RESOLUTION_720P,
+            qualitySelector = QualitySelector.fromOrderedList(listOf(FHD, HD, SD)),
+            expectedQuality = HD,
+        )
+    }
+
+    @Test
+    fun verifySelectedQuality_setHighestQuality_returnSpecificQuality() {
+        testSelectedQualityIsExpected(
+            streamSpecConfiguredResolution = RESOLUTION_1080P,
+            qualitySelector = QualitySelector.from(HIGHEST),
+            expectedQuality = FHD,
+        )
+    }
+
+    @Test
+    fun verifySelectedQuality_setLowestQuality_returnSpecificQuality() {
+        testSelectedQualityIsExpected(
+            streamSpecConfiguredResolution = RESOLUTION_480P,
+            qualitySelector = QualitySelector.from(LOWEST),
+            expectedQuality = SD,
+        )
+    }
+
+    @Test
+    fun verifySelectedQuality_configuredResolutionNotMatch_returnNearestQuality() {
+        testSelectedQualityIsExpected(
+            streamSpecConfiguredResolution = Size(1920, 1000),
+            qualitySelector = QualitySelector.fromOrderedList(listOf(FHD, HD, SD)),
+            expectedQuality = FHD,
+        )
+    }
+
+    private fun testSelectedQualityIsExpected(
+        streamSpecConfiguredResolution: Size,
+        streamSpecResolution: Size = streamSpecConfiguredResolution,
+        qualitySelector: QualitySelector,
+        profiles: Map<Int, EncoderProfilesProxy> = FULL_QUALITY_PROFILES_MAP,
+        videoCapabilities: VideoCapabilities = FULL_QUALITY_VIDEO_CAPABILITIES,
+        expectedQuality: Quality?
+    ) {
+        // Arrange.
+        setupCamera(profiles = profiles)
+        createCameraUseCaseAdapter()
+        setSuggestedStreamSpec(
+            resolution = streamSpecResolution,
+            originalConfiguredResolution = streamSpecConfiguredResolution
+        )
+        val videoOutput =
+            createVideoOutput(
+                videoCapabilities = videoCapabilities,
+                mediaSpec =
+                    MediaSpec.builder()
+                        .configureVideo { it.setQualitySelector(qualitySelector) }
+                        .build()
+            )
+        val videoCapture = createVideoCapture(videoOutput = videoOutput)
+
+        // Act.
+        addAndAttachUseCases(videoCapture)
+
+        // Assert.
+        assertThat(videoCapture.selectedQuality).isEqualTo(expectedQuality)
+    }
+
     private fun testResolutionInfoContainsExpected(
         resolution: Size,
         sensorRotationDegrees: Int,
@@ -2101,6 +2169,7 @@
 
     private fun setSuggestedStreamSpec(
         resolution: Size,
+        originalConfiguredResolution: Size? = null,
         expectedFrameRate: Range<Int> = StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED,
         dynamicRange: DynamicRange? = null
     ) {
@@ -2109,6 +2178,9 @@
                 .apply {
                     setExpectedFrameRateRange(expectedFrameRate)
                     dynamicRange?.let { setDynamicRange(dynamicRange) }
+                    originalConfiguredResolution?.let {
+                        setOriginalConfiguredResolution(originalConfiguredResolution)
+                    }
                 }
                 .build()
         )