Implement new ResolutionSelector logic in SupportedSurfaceCombination class

- Add new SupportedOutputSizesCollector to collect the resolution candidate list for ResolutionSelector logic
- Refactor the existing code of getSupportedOutputSizes() function into the following steps of functions:
   - collectResolutionCandidateList
   - filterOutResolutionCandidateListBySettings
   - sortResolutionCandidateListByResolutionSelector
- Support high resolution and max resolution settings
- Change the logic of max resolution checking that will compare the length of the deminsion edges instead of area
- Merge Preview's max resolution setting to ResolutionSelector when ResolutionSelector is used to set the resolution settings
- Merge ImageAnalysis's analyzer resolution to ResolutionSelector when ResolutionSelector is used to set the resolution settings
- Remove legacy video tests from SupportedSurfaceCombinationTest

Bug: 247471840, 247491400, 247492383
Test: SupportedSurfaceCombinationTest
Change-Id: I9529bcd0e459acd780680a2be1d4c8d0c6967c58
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedOutputSizesCollector.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedOutputSizesCollector.java
new file mode 100644
index 0000000..90b261b
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedOutputSizesCollector.java
@@ -0,0 +1,736 @@
+/*
+ * Copyright 2022 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.camera2.internal;
+
+import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_16_9;
+import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_3_4;
+import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_4_3;
+import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_9_16;
+import static androidx.camera.core.impl.utils.AspectRatioUtil.hasMatchingAspectRatio;
+
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.os.Build;
+import android.util.Rational;
+import android.util.Size;
+
+import androidx.annotation.DoNotInline;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.camera2.internal.compat.workaround.ExcludedSupportedSizesContainer;
+import androidx.camera.camera2.internal.compat.workaround.ResolutionCorrector;
+import androidx.camera.camera2.internal.compat.workaround.TargetAspectRatio;
+import androidx.camera.core.AspectRatio;
+import androidx.camera.core.Logger;
+import androidx.camera.core.ResolutionSelector;
+import androidx.camera.core.impl.ImageFormatConstants;
+import androidx.camera.core.impl.ImageOutputConfig;
+import androidx.camera.core.impl.SizeCoordinate;
+import androidx.camera.core.impl.SurfaceConfig;
+import androidx.camera.core.impl.utils.AspectRatioUtil;
+import androidx.camera.core.impl.utils.CameraOrientationUtil;
+import androidx.camera.core.impl.utils.CompareSizesByArea;
+import androidx.camera.core.internal.utils.SizeUtil;
+import androidx.core.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The supported output sizes collector to help collect the available resolution candidate list
+ * according to the use case config and the following settings in {@link ResolutionSelector}:
+ *
+ * 1. Preferred aspect ratio
+ * 2. Preferred resolution
+ * 3. Max resolution
+ * 4. Is high resolution enabled
+ *
+ * The problematic resolutions retrieved from {@link ExcludedSupportedSizesContainer} will also
+ * be execulded.
+ */
+@RequiresApi(21)
+final class SupportedOutputSizesCollector {
+    private static final String TAG = "SupportedOutputSizesCollector";
+    private final String mCameraId;
+    @NonNull
+    private final CameraCharacteristicsCompat mCharacteristics;
+    @NonNull
+    private final DisplayInfoManager mDisplayInfoManager;
+    private final ResolutionCorrector mResolutionCorrector = new ResolutionCorrector();
+    private final Map<Integer, Size[]> mOutputSizesCache = new HashMap<>();
+    private final Map<Integer, Size[]> mHighResolutionOutputSizesCache = new HashMap<>();
+    private final Map<Integer, Size> mMaxSizeCache = new HashMap<>();
+    private final ExcludedSupportedSizesContainer mExcludedSupportedSizesContainer;
+    private final Map<Integer, List<Size>> mExcludedSizeListCache = new HashMap<>();
+    private final boolean mIsSensorLandscapeResolution;
+    private final boolean mIsBurstCaptureSupported;
+    private final Size mActiveArraySize;
+    private final int mSensorOrientation;
+    private final int mLensFacing;
+
+    SupportedOutputSizesCollector(@NonNull String cameraId,
+            @NonNull CameraCharacteristicsCompat cameraCharacteristics,
+            @NonNull DisplayInfoManager displayInfoManager) {
+        mCameraId = cameraId;
+        mCharacteristics = cameraCharacteristics;
+        mDisplayInfoManager = displayInfoManager;
+
+        mExcludedSupportedSizesContainer = new ExcludedSupportedSizesContainer(cameraId);
+
+        mIsSensorLandscapeResolution = isSensorLandscapeResolution(mCharacteristics);
+        mIsBurstCaptureSupported = isBurstCaptureSupported();
+
+        Rect rect = mCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+        mActiveArraySize = rect != null ? new Size(rect.width(), rect.height()) : null;
+
+        mSensorOrientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+        mLensFacing = mCharacteristics.get(CameraCharacteristics.LENS_FACING);
+    }
+
+    /**
+     * Collects and sorts the resolution candidate list by the following steps:
+     *
+     * 1. Collects the candidate list by the high resolution enable setting.
+     * 2. Filters out the candidate list according to the min size bound, max resolution or
+     * excluded resolution quirk.
+     * 3. Sorts the candidate list according to the rules of legacy resolution API or new
+     * Resolution API.
+     * 4. Forces select specific resolutions according to ResolutionCorrector workaround.
+     */
+    @NonNull
+    List<Size> getSupportedOutputSizes(@NonNull ResolutionSelector resolutionSelector,
+            int imageFormat, @Nullable Size miniBoundingSize,
+            @Nullable Size[] customizedSupportSizes) {
+        // 1. Collects the candidate list by the high resolution enable setting.
+        List<Size> resolutionCandidateList = collectResolutionCandidateList(resolutionSelector,
+                imageFormat, customizedSupportSizes);
+
+        // 2. Filters out the candidate list according to the min size bound, max resolution or
+        // excluded resolution quirk.
+        resolutionCandidateList = filterOutResolutionCandidateListBySettings(
+                resolutionCandidateList, resolutionSelector, imageFormat);
+
+        // 3. Sorts the candidate list according to the rules of new Resolution API.
+        resolutionCandidateList = sortResolutionCandidateListByResolutionSelector(
+                resolutionCandidateList, resolutionSelector,
+                mDisplayInfoManager.getMaxSizeDisplay().getRotation(), miniBoundingSize);
+
+        // 4. Forces select specific resolutions according to ResolutionCorrector workaround.
+        resolutionCandidateList = mResolutionCorrector.insertOrPrioritize(
+                SurfaceConfig.getConfigType(imageFormat), resolutionCandidateList);
+
+        return resolutionCandidateList;
+    }
+
+    /**
+     * Collects the resolution candidate list.
+     *
+     * 1. Customized supported resolutions list will be returned when it exists
+     * 2. Otherwise, the sizes retrieved from {@link StreamConfigurationMap#getOutputSizes(int)}
+     * will be the base of the resolution candidate list.
+     * 3. High resolution sizes retrieved from
+     * {@link StreamConfigurationMap#getHighResolutionOutputSizes(int)} will be included when
+     * {@link ResolutionSelector#isHighResolutionEnabled()} returns true.
+     *
+     * The returned list will be sorted in descending order and duplicate items will be removed.
+     */
+    @NonNull
+    private List<Size> collectResolutionCandidateList(
+            @NonNull ResolutionSelector resolutionSelector, int imageFormat,
+            @Nullable Size[] customizedSupportedSizes) {
+        Size[] outputSizes = customizedSupportedSizes;
+
+        if (outputSizes == null) {
+            boolean highResolutionEnabled = resolutionSelector.isHighResolutionEnabled();
+            outputSizes = getAllOutputSizesByFormat(imageFormat, highResolutionEnabled);
+        }
+
+        // Sort the output sizes. The Comparator result must be reversed to have a descending order
+        // result.
+        Arrays.sort(outputSizes, new CompareSizesByArea(true));
+
+        // Removes the duplicate items
+        List<Size> resultList = new ArrayList<>();
+        for (Size size: outputSizes) {
+            if (!resultList.contains(size)) {
+                resultList.add(size);
+            }
+        }
+
+        if (resultList.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Resolution candidate list is empty when collecting by the settings!");
+        }
+
+        return resultList;
+    }
+
+    /**
+     * Filters out the resolution candidate list by the max resolution setting.
+     *
+     * The input size list should have been sorted in descending order.
+     */
+    private List<Size> filterOutResolutionCandidateListBySettings(
+            @NonNull List<Size> resolutionCandidateList,
+            @NonNull ResolutionSelector resolutionSelector, int imageFormat) {
+        // Retrieves the max resolution setting. When ResolutionSelector is used, all resolution
+        // selection logic should depend on ResolutionSelector's settings.
+        Size maxResolution = resolutionSelector.getMaxResolution();
+
+        // Filter out the resolution candidate list by the max resolution. Sizes that any edge
+        // exceeds the max resolution will be filtered out.
+        List<Size> resultList;
+
+        if (maxResolution == null) {
+            resultList = new ArrayList<>(resolutionCandidateList);
+        } else {
+            resultList = new ArrayList<>();
+            for (Size outputSize : resolutionCandidateList) {
+                if (!SizeUtil.isLongerInAnyEdge(outputSize, maxResolution)) {
+                    resultList.add(outputSize);
+                }
+            }
+        }
+
+        resultList = excludeProblematicSizes(resultList, imageFormat);
+
+        if (resultList.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Resolution candidate list is empty after filtering out by the settings!");
+        }
+
+        return resultList;
+    }
+
+    /**
+     * Sorts the resolution candidate list according to the new ResolutionSelector API logic.
+     *
+     * The list will be sorted by the following order:
+     * 1. size of preferred resolution
+     * 2. a resolution with preferred aspect ratio, is not smaller than, and is closest to the
+     * preferred resolution.
+     * 3. resolutions with preferred aspect ratio and is smaller than the preferred resolution
+     * size in descending order of resolution area size.
+     * 4. Other sizes sorted by CompareAspectRatiosByMappingAreaInFullFovAspectRatioSpace and
+     * area size.
+     */
+    @NonNull
+    private List<Size> sortResolutionCandidateListByResolutionSelector(
+            @NonNull List<Size> resolutionCandidateList,
+            @NonNull ResolutionSelector resolutionSelector,
+            @ImageOutputConfig.RotationValue int targetRotation,
+            @Nullable Size miniBoundingSize) {
+        Rational aspectRatio = getTargetAspectRatioByResolutionSelector(resolutionSelector);
+        Preconditions.checkNotNull(aspectRatio, "ResolutionSelector should also have aspect ratio"
+                + " value.");
+
+        Size targetSize = getTargetSizeByResolutionSelector(resolutionSelector, targetRotation,
+                mSensorOrientation, mLensFacing);
+        List<Size> resultList = sortResolutionCandidateListByTargetAspectRatioAndSize(
+                resolutionCandidateList, aspectRatio, miniBoundingSize);
+
+        // Moves the target size to the first position if it exists in the resolution candidate
+        // list and there is no quirk that needs to select specific aspect ratio sizes in priority.
+        if (resultList.contains(targetSize) && canResolutionBeMovedToHead(targetSize)) {
+            resultList.remove(targetSize);
+            resultList.add(0, targetSize);
+        }
+
+        return resultList;
+    }
+
+    @NonNull
+    private Size[] getAllOutputSizesByFormat(int imageFormat, boolean highResolutionEnabled) {
+        Size[] outputs = mOutputSizesCache.get(imageFormat);
+        if (outputs == null) {
+            outputs = doGetOutputSizesByFormat(imageFormat);
+            mOutputSizesCache.put(imageFormat, outputs);
+        }
+
+        Size[] highResolutionOutputs = null;
+
+        // A device that does not support the BURST_CAPTURE capability,
+        // StreamConfigurationMap#getHighResolutionOutputSizes() will return null.
+        if (highResolutionEnabled && mIsBurstCaptureSupported) {
+            highResolutionOutputs = mHighResolutionOutputSizesCache.get(imageFormat);
+
+            // High resolution output sizes list may be empty. If it is empty and cached in the
+            // map, don't need to query it again.
+            if (highResolutionOutputs == null && !mHighResolutionOutputSizesCache.containsKey(
+                    imageFormat)) {
+                highResolutionOutputs = doGetHighResolutionOutputSizesByFormat(imageFormat);
+                mHighResolutionOutputSizesCache.put(imageFormat, highResolutionOutputs);
+            }
+        }
+
+        // Combines output sizes if high resolution sizes list is not empty.
+        if (highResolutionOutputs != null) {
+            Size[] allOutputs = Arrays.copyOf(highResolutionOutputs,
+                    highResolutionOutputs.length + outputs.length);
+            System.arraycopy(outputs, 0, allOutputs, highResolutionOutputs.length, outputs.length);
+            outputs = allOutputs;
+        }
+
+        return outputs;
+    }
+
+    @NonNull
+    private Size[] doGetOutputSizesByFormat(int imageFormat) {
+        Size[] outputSizes;
+
+        StreamConfigurationMap map =
+                mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+        if (map == null) {
+            throw new IllegalArgumentException("Can not retrieve SCALER_STREAM_CONFIGURATION_MAP");
+        }
+
+        if (Build.VERSION.SDK_INT < 23
+                && imageFormat == ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE) {
+            // This is a little tricky that 0x22 that is internal defined in
+            // StreamConfigurationMap.java to be equal to ImageFormat.PRIVATE that is public
+            // after Android level 23 but not public in Android L. Use {@link SurfaceTexture}
+            // or {@link MediaCodec} will finally mapped to 0x22 in StreamConfigurationMap to
+            // retrieve the output sizes information.
+            outputSizes = map.getOutputSizes(SurfaceTexture.class);
+        } else {
+            outputSizes = map.getOutputSizes(imageFormat);
+        }
+
+        if (outputSizes == null) {
+            throw new IllegalArgumentException(
+                    "Can not get supported output size for the format: " + imageFormat);
+        }
+
+        return outputSizes;
+    }
+
+    @Nullable
+    private Size[] doGetHighResolutionOutputSizesByFormat(int imageFormat) {
+        if (Build.VERSION.SDK_INT < 23) {
+            return null;
+        }
+
+        Size[] outputSizes;
+
+        StreamConfigurationMap map =
+                mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+        if (map == null) {
+            throw new IllegalArgumentException("Can not retrieve SCALER_STREAM_CONFIGURATION_MAP");
+        }
+
+        outputSizes = Api23Impl.getHighResolutionOutputSizes(map, imageFormat);
+
+        return outputSizes;
+    }
+
+    /**
+     * Returns the target aspect ratio value corrected by quirks.
+     *
+     * The final aspect ratio is determined by the following order:
+     * 1. The aspect ratio returned by {@link TargetAspectRatio} if it is
+     * {@link TargetAspectRatio#RATIO_4_3}, {@link TargetAspectRatio#RATIO_16_9} or
+     * {@link TargetAspectRatio#RATIO_MAX_JPEG}.
+     * 2. The use case's original aspect ratio if {@link TargetAspectRatio} returns
+     * {@link TargetAspectRatio#RATIO_ORIGINAL} and the use case has target aspect ratio setting.
+     *
+     * @param resolutionSelector the resolution selector of the use case.
+     */
+    @Nullable
+    private Rational getTargetAspectRatioByResolutionSelector(
+            @NonNull ResolutionSelector resolutionSelector) {
+        Rational outputRatio = getTargetAspectRatioFromQuirk();
+
+        if (outputRatio == null) {
+            @AspectRatio.Ratio int aspectRatio = resolutionSelector.getPreferredAspectRatio();
+            switch (aspectRatio) {
+                case AspectRatio.RATIO_4_3:
+                    outputRatio = mIsSensorLandscapeResolution ? ASPECT_RATIO_4_3
+                            : ASPECT_RATIO_3_4;
+                    break;
+                case AspectRatio.RATIO_16_9:
+                    outputRatio = mIsSensorLandscapeResolution ? ASPECT_RATIO_16_9
+                            : ASPECT_RATIO_9_16;
+                    break;
+                default:
+                    Logger.e(TAG, "Undefined target aspect ratio: " + aspectRatio);
+            }
+        }
+        return outputRatio;
+    }
+
+    /**
+     * Returns the restricted target aspect ratio value from quirk. The returned value can be
+     * null which means that no quirk to restrict the use case to use a specific target aspect
+     * ratio value.
+     */
+    @Nullable
+    private Rational getTargetAspectRatioFromQuirk() {
+        Rational outputRatio = null;
+
+        // Gets the corrected aspect ratio due to device constraints or null if no correction is
+        // needed.
+        @TargetAspectRatio.Ratio int targetAspectRatio =
+                new TargetAspectRatio().get(mCameraId, mCharacteristics);
+        switch (targetAspectRatio) {
+            case TargetAspectRatio.RATIO_4_3:
+                outputRatio = mIsSensorLandscapeResolution ? ASPECT_RATIO_4_3 : ASPECT_RATIO_3_4;
+                break;
+            case TargetAspectRatio.RATIO_16_9:
+                outputRatio = mIsSensorLandscapeResolution ? ASPECT_RATIO_16_9 : ASPECT_RATIO_9_16;
+                break;
+            case TargetAspectRatio.RATIO_MAX_JPEG:
+                Size maxJpegSize = fetchMaxNormalOutputSize(ImageFormat.JPEG);
+                outputRatio = new Rational(maxJpegSize.getWidth(), maxJpegSize.getHeight());
+                break;
+            case TargetAspectRatio.RATIO_ORIGINAL:
+                break;
+        }
+
+        return outputRatio;
+    }
+
+    @Nullable
+    static Size getTargetSizeByResolutionSelector(@NonNull ResolutionSelector resolutionSelector,
+            int targetRotation, int sensorOrientation, int lensFacing) {
+        Size targetSize = resolutionSelector.getPreferredResolution();
+
+        // Calibrate targetSize by the target rotation value if it is set by the Android View
+        // coordinate orientation.
+        if (resolutionSelector.getSizeCoordinate() == SizeCoordinate.ANDROID_VIEW) {
+            targetSize = flipSizeByRotation(targetSize, targetRotation, lensFacing,
+                    sensorOrientation);
+        }
+        return targetSize;
+    }
+
+    private static boolean isRotationNeeded(int targetRotation, int lensFacing,
+            int sensorOrientation) {
+        int relativeRotationDegrees =
+                CameraOrientationUtil.surfaceRotationToDegrees(targetRotation);
+
+        // Currently this assumes that a back-facing camera is always opposite to the screen.
+        // This may not be the case for all devices, so in the future we may need to handle that
+        // scenario.
+        boolean isOppositeFacingScreen = CameraCharacteristics.LENS_FACING_BACK == lensFacing;
+
+        int sensorRotationDegrees = CameraOrientationUtil.getRelativeImageRotation(
+                relativeRotationDegrees,
+                sensorOrientation,
+                isOppositeFacingScreen);
+        return sensorRotationDegrees == 90 || sensorRotationDegrees == 270;
+    }
+
+    @NonNull
+    private List<Size> excludeProblematicSizes(@NonNull List<Size> resolutionCandidateList,
+            int imageFormat) {
+        List<Size> excludedSizes = fetchExcludedSizes(imageFormat);
+        resolutionCandidateList.removeAll(excludedSizes);
+        return resolutionCandidateList;
+    }
+
+    @NonNull
+    private List<Size> fetchExcludedSizes(int imageFormat) {
+        List<Size> excludedSizes = mExcludedSizeListCache.get(imageFormat);
+
+        if (excludedSizes == null) {
+            excludedSizes = mExcludedSupportedSizesContainer.get(imageFormat);
+            mExcludedSizeListCache.put(imageFormat, excludedSizes);
+        }
+
+        return excludedSizes;
+    }
+
+    /**
+     * Sorts the resolution candidate list according to the target aspect ratio and size settings.
+     *
+     * 1. The resolution candidate list will be grouped by aspect ratio.
+     * 2. Each group only keeps one size which is not smaller than the target size.
+     * 3. The aspect ratios of groups will be sorted against to the target aspect ratio setting by
+     * CompareAspectRatiosByMappingAreaInFullFovAspectRatioSpace.
+     * 4. Concatenate all sizes as the result list
+     */
+    @NonNull
+    private List<Size> sortResolutionCandidateListByTargetAspectRatioAndSize(
+            @NonNull List<Size> resolutionCandidateList, @NonNull Rational aspectRatio,
+            @Nullable Size miniBoundingSize) {
+        // Rearrange the supported size to put the ones with the same aspect ratio in the front
+        // of the list and put others in the end from large to small. Some low end devices may
+        // not able to get an supported resolution that match the preferred aspect ratio.
+
+        // Group output sizes by aspect ratio.
+        Map<Rational, List<Size>> aspectRatioSizeListMap =
+                groupSizesByAspectRatio(resolutionCandidateList);
+
+        // If the target resolution is set, use it to remove unnecessary larger sizes.
+        if (miniBoundingSize != null) {
+            // Remove unnecessary larger sizes from each aspect ratio size list
+            for (Rational key : aspectRatioSizeListMap.keySet()) {
+                removeSupportedSizesByMiniBoundingSize(aspectRatioSizeListMap.get(key),
+                        miniBoundingSize);
+            }
+        }
+
+        // Sort the aspect ratio key set by the target aspect ratio.
+        List<Rational> aspectRatios = new ArrayList<>(aspectRatioSizeListMap.keySet());
+        Rational fullFovRatio = mActiveArraySize != null ? new Rational(
+                mActiveArraySize.getWidth(), mActiveArraySize.getHeight()) : null;
+        Collections.sort(aspectRatios,
+                new AspectRatioUtil.CompareAspectRatiosByMappingAreaInFullFovAspectRatioSpace(
+                        aspectRatio, fullFovRatio));
+
+        List<Size> resultList = new ArrayList<>();
+
+        // Put available sizes into final result list by aspect ratio distance to target ratio.
+        for (Rational rational : aspectRatios) {
+            for (Size size : aspectRatioSizeListMap.get(rational)) {
+                // A size may exist in multiple groups in mod16 condition. Keep only one in
+                // the final list.
+                if (!resultList.contains(size)) {
+                    resultList.add(size);
+                }
+            }
+        }
+
+        return resultList;
+    }
+
+    /**
+     * Returns {@code true} if the input resolution can be moved to the head of resolution
+     * candidate list.
+     *
+     * The resolution possibly can't be moved to head due to some quirks that sizes of
+     * specific aspect ratio must be used to avoid problems.
+     */
+    private boolean canResolutionBeMovedToHead(@NonNull Size resolution) {
+        @TargetAspectRatio.Ratio int targetAspectRatio =
+                new TargetAspectRatio().get(mCameraId, mCharacteristics);
+
+        switch (targetAspectRatio) {
+            case TargetAspectRatio.RATIO_4_3:
+                return hasMatchingAspectRatio(resolution, ASPECT_RATIO_4_3);
+            case TargetAspectRatio.RATIO_16_9:
+                return hasMatchingAspectRatio(resolution, ASPECT_RATIO_16_9);
+            case TargetAspectRatio.RATIO_MAX_JPEG:
+                Size maxJpegSize = fetchMaxNormalOutputSize(ImageFormat.JPEG);
+                Rational maxJpegRatio = new Rational(maxJpegSize.getWidth(),
+                        maxJpegSize.getHeight());
+                return hasMatchingAspectRatio(resolution, maxJpegRatio);
+        }
+
+        return true;
+    }
+
+    private Size fetchMaxNormalOutputSize(int imageFormat) {
+        Size size = mMaxSizeCache.get(imageFormat);
+        if (size != null) {
+            return size;
+        }
+        Size maxSize = getMaxNormalOutputSizeByFormat(imageFormat);
+        mMaxSizeCache.put(imageFormat, maxSize);
+        return maxSize;
+    }
+
+    /**
+     * Gets max normal supported output size for specific image format.
+     *
+     * <p>Normal supported output sizes mean the sizes retrieved by the
+     * {@link StreamConfigurationMap#getOutputSizes(int)}. The high resolution sizes retrieved by
+     * the {@link StreamConfigurationMap#getHighResolutionOutputSizes(int)} are not included.
+     *
+     * @param imageFormat the image format info
+     * @return the max normal supported output size for the image format
+     */
+    private Size getMaxNormalOutputSizeByFormat(int imageFormat) {
+        Size[] outputSizes = getAllOutputSizesByFormat(imageFormat, false);
+
+        return SizeUtil.getMaxSize(Arrays.asList(outputSizes));
+    }
+
+    private boolean isBurstCaptureSupported() {
+        int[] availableCapabilities =
+                mCharacteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+
+        if (availableCapabilities != null) {
+            for (int capability : availableCapabilities) {
+                if (capability
+                        == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////
+    // The following functions can be reused by the legacy resolution selection logic. The can be
+    // changed as private function after the legacy resolution API is completely removed.
+    //////////////////////////////////////////////////////////////////////////////////////////
+
+    // Use target rotation to calibrate the size.
+    @Nullable
+    static Size flipSizeByRotation(@Nullable Size size, int targetRotation, int lensFacing,
+            int sensorOrientation) {
+        Size outputSize = size;
+        // Calibrates the size with the display and sensor rotation degrees values.
+        if (size != null && isRotationNeeded(targetRotation, lensFacing, sensorOrientation)) {
+            outputSize = new Size(/* width= */size.getHeight(), /* height= */size.getWidth());
+        }
+        return outputSize;
+    }
+
+    static Map<Rational, List<Size>> groupSizesByAspectRatio(List<Size> sizes) {
+        Map<Rational, List<Size>> aspectRatioSizeListMap = new HashMap<>();
+
+        List<Rational> aspectRatioKeys = getResolutionListGroupingAspectRatioKeys(sizes);
+
+        for (Rational aspectRatio: aspectRatioKeys) {
+            aspectRatioSizeListMap.put(aspectRatio, new ArrayList<>());
+        }
+
+        for (Size outputSize : sizes) {
+            for (Rational key : aspectRatioSizeListMap.keySet()) {
+                // Put the size into all groups that is matched in mod16 condition since a size
+                // may match multiple aspect ratio in mod16 algorithm.
+                if (hasMatchingAspectRatio(outputSize, key)) {
+                    aspectRatioSizeListMap.get(key).add(outputSize);
+                }
+            }
+        }
+
+        return aspectRatioSizeListMap;
+    }
+
+    /**
+     * Returns the grouping aspect ratio keys of the input resolution list.
+     *
+     * <p>Some sizes might be mod16 case. When grouping, those sizes will be grouped into an
+     * existing aspect ratio group if the aspect ratio can match by the mod16 rule.
+     */
+    @NonNull
+    static List<Rational> getResolutionListGroupingAspectRatioKeys(
+            @NonNull List<Size> resolutionCandidateList) {
+        List<Rational> aspectRatios = new ArrayList<>();
+
+        // Adds the default 4:3 and 16:9 items first to avoid their mod16 sizes to create
+        // additional items.
+        aspectRatios.add(ASPECT_RATIO_4_3);
+        aspectRatios.add(ASPECT_RATIO_16_9);
+
+        // Tries to find the aspect ratio which the target size belongs to.
+        for (Size size : resolutionCandidateList) {
+            Rational newRatio = new Rational(size.getWidth(), size.getHeight());
+            boolean aspectRatioFound = aspectRatios.contains(newRatio);
+
+            // The checking size might be a mod16 size which can be mapped to an existing aspect
+            // ratio group.
+            if (!aspectRatioFound) {
+                boolean hasMatchingAspectRatio = false;
+                for (Rational aspectRatio : aspectRatios) {
+                    if (hasMatchingAspectRatio(size, aspectRatio)) {
+                        hasMatchingAspectRatio = true;
+                        break;
+                    }
+                }
+                if (!hasMatchingAspectRatio) {
+                    aspectRatios.add(newRatio);
+                }
+            }
+        }
+
+        return aspectRatios;
+    }
+
+    /**
+     * Removes unnecessary sizes by target size.
+     *
+     * <p>If the target resolution is set, a size that is equal to or closest to the target
+     * resolution will be selected. If the list includes more than one size equal to or larger
+     * than the target resolution, only one closest size needs to be kept. The other larger sizes
+     * can be removed so that they won't be selected to use.
+     *
+     * @param supportedSizesList The list should have been sorted in descending order.
+     * @param miniBoundingSize The target size used to remove unnecessary sizes.
+     */
+    static void removeSupportedSizesByMiniBoundingSize(List<Size> supportedSizesList,
+            Size miniBoundingSize) {
+        if (supportedSizesList == null || supportedSizesList.isEmpty()) {
+            return;
+        }
+
+        int indexBigEnough = -1;
+        List<Size> removeSizes = new ArrayList<>();
+
+        // Get the index of the item that is equal to or closest to the target size.
+        for (int i = 0; i < supportedSizesList.size(); i++) {
+            Size outputSize = supportedSizesList.get(i);
+            if (outputSize.getWidth() >= miniBoundingSize.getWidth()
+                    && outputSize.getHeight() >= miniBoundingSize.getHeight()) {
+                // New big enough item closer to the target size is found. Adding the previous
+                // one into the sizes list that will be removed.
+                if (indexBigEnough >= 0) {
+                    removeSizes.add(supportedSizesList.get(indexBigEnough));
+                }
+
+                indexBigEnough = i;
+            } else {
+                break;
+            }
+        }
+
+        // Remove the unnecessary items that are larger than the item closest to the target size.
+        supportedSizesList.removeAll(removeSizes);
+    }
+
+    static boolean isSensorLandscapeResolution(
+            @NonNull CameraCharacteristicsCompat characteristicsCompat) {
+        Size pixelArraySize =
+                characteristicsCompat.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE);
+
+        // Make the default value is true since usually the sensor resolution is landscape.
+        return pixelArraySize == null || pixelArraySize.getWidth() >= pixelArraySize.getHeight();
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////
+    // The above functions can be reused by the legacy resolution selection logic. The can be
+    // changed as private function after the legacy resolution API is completely removed.
+    //////////////////////////////////////////////////////////////////////////////////////////
+
+    @RequiresApi(23)
+    private static class Api23Impl {
+        private Api23Impl() {
+            // This class is not instantiable.
+        }
+
+        @DoNotInline
+        static Size[] getHighResolutionOutputSizes(StreamConfigurationMap streamConfigurationMap,
+                int format) {
+            return streamConfigurationMap.getHighResolutionOutputSizes(format);
+        }
+    }
+}
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 15f8049..659970d 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
@@ -16,6 +16,12 @@
 
 package androidx.camera.camera2.internal;
 
+import static androidx.camera.camera2.internal.SupportedOutputSizesCollector.flipSizeByRotation;
+import static androidx.camera.camera2.internal.SupportedOutputSizesCollector.getResolutionListGroupingAspectRatioKeys;
+import static androidx.camera.camera2.internal.SupportedOutputSizesCollector.getTargetSizeByResolutionSelector;
+import static androidx.camera.camera2.internal.SupportedOutputSizesCollector.groupSizesByAspectRatio;
+import static androidx.camera.camera2.internal.SupportedOutputSizesCollector.isSensorLandscapeResolution;
+import static androidx.camera.camera2.internal.SupportedOutputSizesCollector.removeSupportedSizesByMiniBoundingSize;
 import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_16_9;
 import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_3_4;
 import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_4_3;
@@ -55,6 +61,7 @@
 import androidx.camera.core.AspectRatio;
 import androidx.camera.core.CameraUnavailableException;
 import androidx.camera.core.Logger;
+import androidx.camera.core.ResolutionSelector;
 import androidx.camera.core.impl.AttachedSurfaceInfo;
 import androidx.camera.core.impl.ImageFormatConstants;
 import androidx.camera.core.impl.ImageOutputConfig;
@@ -63,7 +70,6 @@
 import androidx.camera.core.impl.SurfaceSizeDefinition;
 import androidx.camera.core.impl.UseCaseConfig;
 import androidx.camera.core.impl.utils.AspectRatioUtil;
-import androidx.camera.core.impl.utils.CameraOrientationUtil;
 import androidx.camera.core.impl.utils.CompareSizesByArea;
 import androidx.core.util.Preconditions;
 
@@ -105,8 +111,10 @@
     @NonNull
     private final DisplayInfoManager mDisplayInfoManager;
     private final ResolutionCorrector mResolutionCorrector = new ResolutionCorrector();
-
     private final Size mActiveArraySize;
+    private final int mSensorOrientation;
+    private final int mLensFacing;
+    private final SupportedOutputSizesCollector mSupportedOutputSizesCollector;
 
     SupportedSurfaceCombination(@NonNull Context context, @NonNull String cameraId,
             @NonNull CameraManagerCompat cameraManagerCompat,
@@ -125,7 +133,7 @@
                     CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
             mHardwareLevel = keyValue != null ? keyValue
                     : CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
-            mIsSensorLandscapeResolution = isSensorLandscapeResolution();
+            mIsSensorLandscapeResolution = isSensorLandscapeResolution(mCharacteristics);
         } catch (CameraAccessExceptionCompat e) {
             throw CameraUnavailableExceptionHelper.createFrom(e);
         }
@@ -147,9 +155,15 @@
         Rect rect = mCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
         mActiveArraySize = rect != null ? new Size(rect.width(), rect.height()) : null;
 
+        mSensorOrientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+        mLensFacing = mCharacteristics.get(CameraCharacteristics.LENS_FACING);
+
         generateSupportedCombinationList();
         generateSurfaceSizeDefinition();
         checkCustomization();
+
+        mSupportedOutputSizesCollector = new SupportedOutputSizesCollector(mCameraId,
+                mCharacteristics, mDisplayInfoManager);
     }
 
     String getCameraId() {
@@ -413,10 +427,30 @@
     List<Size> getSupportedOutputSizes(@NonNull UseCaseConfig<?> config) {
         int imageFormat = config.getInputFormat();
         ImageOutputConfig imageOutputConfig = (ImageOutputConfig) config;
+        ResolutionSelector resolutionSelector = imageOutputConfig.getResolutionSelector(null);
+
+        // Directly returns the output sizes retrieved from SupportedOutputSizesCollector when
+        // ResolutionSelector is used.
+        if (resolutionSelector != null) {
+            Size miniBoundingSize = imageOutputConfig.getDefaultResolution(null);
+
+            if (resolutionSelector.getPreferredResolution() != null) {
+                miniBoundingSize = getTargetSizeByResolutionSelector(resolutionSelector,
+                        mDisplayInfoManager.getMaxSizeDisplay().getRotation(), mSensorOrientation,
+                        mLensFacing);
+            }
+
+            return mSupportedOutputSizesCollector.getSupportedOutputSizes(resolutionSelector,
+                    imageFormat, miniBoundingSize,
+                    getCustomizedSupportSizesFromConfig(imageFormat, imageOutputConfig));
+        }
+
         Size[] outputSizes = getCustomizedSupportSizesFromConfig(imageFormat, imageOutputConfig);
         if (outputSizes == null) {
             outputSizes = getAllOutputSizesByFormat(imageFormat);
         }
+        outputSizes = excludeProblematicSizesAndSort(outputSizes, imageFormat);
+
         List<Size> outputSizeCandidates = new ArrayList<>();
         Size maxSize = imageOutputConfig.getMaxResolution(null);
         Size maxOutputSizeByFormat = getMaxOutputSizeByFormat(imageFormat);
@@ -474,7 +508,7 @@
 
             // If the target resolution is set, use it to remove unnecessary larger sizes.
             if (targetSize != null) {
-                removeSupportedSizesByTargetSize(supportedResolutions, targetSize);
+                removeSupportedSizesByMiniBoundingSize(supportedResolutions, targetSize);
             }
         } else {
             // Rearrange the supported size to put the ones with the same aspect ratio in the front
@@ -488,7 +522,8 @@
             if (targetSize != null) {
                 // Remove unnecessary larger sizes from each aspect ratio size list
                 for (Rational key : aspectRatioSizeListMap.keySet()) {
-                    removeSupportedSizesByTargetSize(aspectRatioSizeListMap.get(key), targetSize);
+                    removeSupportedSizesByMiniBoundingSize(aspectRatioSizeListMap.get(key),
+                            targetSize);
                 }
             }
 
@@ -524,7 +559,8 @@
         int targetRotation = imageOutputConfig.getTargetRotation(Surface.ROTATION_0);
         // Calibrate targetSize by the target rotation value.
         Size targetSize = imageOutputConfig.getTargetResolution(null);
-        targetSize = flipSizeByRotation(targetSize, targetRotation);
+        targetSize = flipSizeByRotation(targetSize, targetRotation, mLensFacing,
+                mSensorOrientation);
         return targetSize;
     }
 
@@ -555,152 +591,6 @@
         return new Rational(targetSize.getWidth(), targetSize.getHeight());
     }
 
-    /**
-     * Returns the grouping aspect ratio keys of the input resolution list.
-     *
-     * <p>Some sizes might be mod16 case. When grouping, those sizes will be grouped into an
-     * existing aspect ratio group if the aspect ratio can match by the mod16 rule.
-     */
-    @NonNull
-    private List<Rational> getResolutionListGroupingAspectRatioKeys(
-            @NonNull List<Size> resolutionCandidateList) {
-        List<Rational> aspectRatios = new ArrayList<>();
-
-        // Adds the default 4:3 and 16:9 items first to avoid their mod16 sizes to create
-        // additional items.
-        aspectRatios.add(ASPECT_RATIO_4_3);
-        aspectRatios.add(ASPECT_RATIO_16_9);
-
-        // Tries to find the aspect ratio which the target size belongs to.
-        for (Size size : resolutionCandidateList) {
-            Rational newRatio = new Rational(size.getWidth(), size.getHeight());
-            boolean aspectRatioFound = aspectRatios.contains(newRatio);
-
-            // The checking size might be a mod16 size which can be mapped to an existing aspect
-            // ratio group.
-            if (!aspectRatioFound) {
-                boolean hasMatchingAspectRatio = false;
-                for (Rational aspectRatio : aspectRatios) {
-                    if (hasMatchingAspectRatio(size, aspectRatio)) {
-                        hasMatchingAspectRatio = true;
-                        break;
-                    }
-                }
-                if (!hasMatchingAspectRatio) {
-                    aspectRatios.add(newRatio);
-                }
-            }
-        }
-
-        return aspectRatios;
-    }
-
-    // Use target rotation to calibrate the size.
-    @Nullable
-    private Size flipSizeByRotation(@Nullable Size size, int targetRotation) {
-        Size outputSize = size;
-        // Calibrates the size with the display and sensor rotation degrees values.
-        if (size != null && isRotationNeeded(targetRotation)) {
-            outputSize = new Size(/* width= */size.getHeight(), /* height= */size.getWidth());
-        }
-        return outputSize;
-    }
-
-    private boolean isRotationNeeded(int targetRotation) {
-        Integer sensorOrientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
-        Preconditions.checkNotNull(sensorOrientation, "Camera HAL in bad state, unable to "
-                + "retrieve the SENSOR_ORIENTATION");
-        int relativeRotationDegrees =
-                CameraOrientationUtil.surfaceRotationToDegrees(targetRotation);
-
-        // Currently this assumes that a back-facing camera is always opposite to the screen.
-        // This may not be the case for all devices, so in the future we may need to handle that
-        // scenario.
-        Integer lensFacing = mCharacteristics.get(CameraCharacteristics.LENS_FACING);
-        Preconditions.checkNotNull(lensFacing, "Camera HAL in bad state, unable to retrieve the "
-                + "LENS_FACING");
-
-        boolean isOppositeFacingScreen = CameraCharacteristics.LENS_FACING_BACK == lensFacing;
-
-        int sensorRotationDegrees = CameraOrientationUtil.getRelativeImageRotation(
-                relativeRotationDegrees,
-                sensorOrientation,
-                isOppositeFacingScreen);
-        return sensorRotationDegrees == 90 || sensorRotationDegrees == 270;
-    }
-
-    private boolean isSensorLandscapeResolution() {
-        Size pixelArraySize =
-                mCharacteristics.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE);
-
-        // Make the default value is true since usually the sensor resolution is landscape.
-        return pixelArraySize != null ? pixelArraySize.getWidth() >= pixelArraySize.getHeight()
-                : true;
-    }
-
-    private Map<Rational, List<Size>> groupSizesByAspectRatio(List<Size> sizes) {
-        Map<Rational, List<Size>> aspectRatioSizeListMap = new HashMap<>();
-
-        List<Rational> aspectRatioKeys = getResolutionListGroupingAspectRatioKeys(sizes);
-
-        for (Rational aspectRatio: aspectRatioKeys) {
-            aspectRatioSizeListMap.put(aspectRatio, new ArrayList<>());
-        }
-
-        for (Size outputSize : sizes) {
-            for (Rational key : aspectRatioSizeListMap.keySet()) {
-                // Put the size into all groups that is matched in mod16 condition since a size
-                // may match multiple aspect ratio in mod16 algorithm.
-                if (hasMatchingAspectRatio(outputSize, key)) {
-                    aspectRatioSizeListMap.get(key).add(outputSize);
-                }
-            }
-        }
-
-        return aspectRatioSizeListMap;
-    }
-
-    /**
-     * Removes unnecessary sizes by target size.
-     *
-     * <p>If the target resolution is set, a size that is equal to or closest to the target
-     * resolution will be selected. If the list includes more than one size equal to or larger
-     * than the target resolution, only one closest size needs to be kept. The other larger sizes
-     * can be removed so that they won't be selected to use.
-     *
-     * @param supportedSizesList The list should have been sorted in descending order.
-     * @param targetSize         The target size used to remove unnecessary sizes.
-     */
-    private void removeSupportedSizesByTargetSize(List<Size> supportedSizesList,
-            Size targetSize) {
-        if (supportedSizesList == null || supportedSizesList.isEmpty()) {
-            return;
-        }
-
-        int indexBigEnough = -1;
-        List<Size> removeSizes = new ArrayList<>();
-
-        // Get the index of the item that is equal to or closest to the target size.
-        for (int i = 0; i < supportedSizesList.size(); i++) {
-            Size outputSize = supportedSizesList.get(i);
-            if (outputSize.getWidth() >= targetSize.getWidth()
-                    && outputSize.getHeight() >= targetSize.getHeight()) {
-                // New big enough item closer to the target size is found. Adding the previous
-                // one into the sizes list that will be removed.
-                if (indexBigEnough >= 0) {
-                    removeSizes.add(supportedSizesList.get(indexBigEnough));
-                }
-
-                indexBigEnough = i;
-            } else {
-                break;
-            }
-        }
-
-        // Remove the unnecessary items that are larger than the item closest to the target size.
-        supportedSizesList.removeAll(removeSizes);
-    }
-
     private List<List<Size>> getAllPossibleSizeArrangements(
             List<List<Size>> supportedOutputSizesList) {
         int totalArrangementsCount = 1;
@@ -755,11 +645,18 @@
     }
 
     @NonNull
-    private Size[] excludeProblematicSizes(@NonNull Size[] outputSizes, int imageFormat) {
+    private Size[] excludeProblematicSizesAndSort(@NonNull Size[] outputSizes, int imageFormat) {
         List<Size> excludedSizes = fetchExcludedSizes(imageFormat);
         List<Size> resultSizesList = new ArrayList<>(Arrays.asList(outputSizes));
         resultSizesList.removeAll(excludedSizes);
-        return resultSizesList.toArray(new Size[0]);
+
+        Size[] resultSizes = resultSizesList.toArray(new Size[0]);
+
+        // Sort the result sizes. The Comparator result must be reversed to have a descending
+        // order result.
+        Arrays.sort(resultSizes, new CompareSizesByArea(true));
+
+        return resultSizes;
     }
 
     @Nullable
@@ -780,14 +677,6 @@
             }
         }
 
-        if (outputSizes != null) {
-            outputSizes = excludeProblematicSizes(outputSizes, imageFormat);
-
-            // Sort the output sizes. The Comparator result must be reversed to have a descending
-            // order result.
-            Arrays.sort(outputSizes, new CompareSizesByArea(true));
-        }
-
         return outputSizes;
     }
 
@@ -830,12 +719,6 @@
                     "Can not get supported output size for the format: " + imageFormat);
         }
 
-        outputSizes = excludeProblematicSizes(outputSizes, imageFormat);
-
-        // Sort the output sizes. The Comparator result must be reversed to have a descending order
-        // result.
-        Arrays.sort(outputSizes, new CompareSizesByArea(true));
-
         return outputSizes;
     }
 
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedOutputSizesCollectorTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedOutputSizesCollectorTest.kt
new file mode 100644
index 0000000..a1101654
--- /dev/null
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedOutputSizesCollectorTest.kt
@@ -0,0 +1,1316 @@
+/*
+ * Copyright 2022 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.camera2.internal
+
+import android.content.Context
+import android.graphics.SurfaceTexture
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraManager
+import android.hardware.camera2.params.StreamConfigurationMap
+import android.media.MediaRecorder
+import android.os.Build
+import android.util.Pair
+import android.util.Size
+import android.view.Surface
+import android.view.WindowManager
+import androidx.camera.camera2.Camera2Config
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat
+import androidx.camera.camera2.internal.compat.CameraManagerCompat
+import androidx.camera.core.AspectRatio
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.CameraX
+import androidx.camera.core.CameraXConfig
+import androidx.camera.core.ImageAnalysis
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.Preview
+import androidx.camera.core.ResolutionSelector
+import androidx.camera.core.UseCase
+import androidx.camera.core.impl.CameraDeviceSurfaceManager
+import androidx.camera.core.impl.ImageOutputConfig
+import androidx.camera.core.impl.SizeCoordinate
+import androidx.camera.core.impl.UseCaseConfigFactory
+import androidx.camera.testing.CameraUtil
+import androidx.camera.testing.CameraXUtil
+import androidx.camera.testing.Configs
+import androidx.camera.testing.fakes.FakeCamera
+import androidx.camera.testing.fakes.FakeCameraFactory
+import androidx.camera.testing.fakes.FakeCameraInfoInternal
+import androidx.camera.testing.fakes.FakeUseCaseConfig
+import androidx.test.core.app.ApplicationProvider
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeUnit
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
+import org.robolectric.ParameterizedRobolectricTestRunner
+import org.robolectric.Shadows
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadow.api.Shadow
+import org.robolectric.shadows.ShadowCameraCharacteristics
+import org.robolectric.shadows.ShadowCameraManager
+
+private const val FAKE_USE_CASE = 0
+private const val PREVIEW_USE_CASE = 1
+private const val IMAGE_CAPTURE_USE_CASE = 2
+private const val IMAGE_ANALYSIS_USE_CASE = 3
+private const val UNKNOWN_ASPECT_RATIO = -1
+private const val DEFAULT_CAMERA_ID = "0"
+private const val SENSOR_ORIENTATION_0 = 0
+private const val SENSOR_ORIENTATION_90 = 90
+private val LANDSCAPE_PIXEL_ARRAY_SIZE = Size(4032, 3024)
+private val PORTRAIT_PIXEL_ARRAY_SIZE = Size(3024, 4032)
+private val DISPLAY_SIZE = Size(720, 1280)
+private val DEFAULT_SUPPORTED_SIZES = arrayOf(
+    Size(4032, 3024), // 4:3
+    Size(3840, 2160), // 16:9
+    Size(1920, 1440), // 4:3
+    Size(1920, 1080), // 16:9
+    Size(1280, 960), // 4:3
+    Size(1280, 720), // 16:9
+    Size(960, 544), // a mod16 version of resolution with 16:9 aspect ratio.
+    Size(800, 450), // 16:9
+    Size(640, 480), // 4:3
+    Size(320, 240), // 4:3
+    Size(320, 180), // 16:9
+    Size(256, 144) // 16:9 For checkSmallSizesAreFilteredOut test.
+)
+
+/** Robolectric test for [SupportedOutputSizesCollector] class */
+@RunWith(ParameterizedRobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class SupportedOutputSizesCollectorTest(
+    private val sizeCoordinate: SizeCoordinate
+) {
+    private val mockCamcorderProfileHelper = Mockito.mock(CamcorderProfileHelper::class.java)
+    private lateinit var cameraManagerCompat: CameraManagerCompat
+    private lateinit var cameraCharacteristicsCompat: CameraCharacteristicsCompat
+    private lateinit var displayInfoManager: DisplayInfoManager
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+    private var cameraFactory: FakeCameraFactory? = null
+    private var useCaseConfigFactory: UseCaseConfigFactory? = null
+
+    @Suppress("DEPRECATION") // defaultDisplay
+    @Before
+    fun setUp() {
+        DisplayInfoManager.releaseInstance()
+        val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+        Shadows.shadowOf(windowManager.defaultDisplay).setRealWidth(DISPLAY_SIZE.width)
+        Shadows.shadowOf(windowManager.defaultDisplay).setRealHeight(DISPLAY_SIZE.height)
+        Mockito.`when`(
+            mockCamcorderProfileHelper.hasProfile(
+                ArgumentMatchers.anyInt(),
+                ArgumentMatchers.anyInt()
+            )
+        ).thenReturn(true)
+
+        displayInfoManager = DisplayInfoManager.getInstance(context)
+    }
+
+    @After
+    fun tearDown() {
+        CameraXUtil.shutdown()[10000, TimeUnit.MILLISECONDS]
+    }
+
+    @Test
+    fun getSupportedOutputSizes_aspectRatio4x3() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredAspectRatio = AspectRatio.RATIO_4_3
+        )
+
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // Matched preferred AspectRatio items, sorted by area size.
+            Size(4032, 3024),
+            Size(1920, 1440),
+            Size(1280, 960),
+            Size(640, 480),
+            Size(320, 240),
+            // Mismatched preferred AspectRatio items, sorted by area size.
+            Size(3840, 2160),
+            Size(1920, 1080),
+            Size(1280, 720),
+            Size(960, 544),
+            Size(800, 450),
+            Size(320, 180),
+            Size(256, 144)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_aspectRatio16x9_InLimitedDevice() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredAspectRatio = AspectRatio.RATIO_16_9
+        )
+
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // Matched preferred AspectRatio items, sorted by area size.
+            Size(3840, 2160),
+            Size(1920, 1080),
+            Size(1280, 720),
+            Size(960, 544),
+            Size(800, 450),
+            Size(320, 180),
+            Size(256, 144),
+            // Mismatched preferred AspectRatio items, sorted by area size.
+            Size(4032, 3024),
+            Size(1920, 1440),
+            Size(1280, 960),
+            Size(640, 480),
+            Size(320, 240)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_aspectRatio16x9_inLegacyDevice() {
+        setupCameraAndInitCameraX()
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredAspectRatio = AspectRatio.RATIO_16_9
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList: List<Size> = if (Build.VERSION.SDK_INT == 21) {
+            listOf(
+                // Matched maximum JPEG resolution AspectRatio items, sorted by area size.
+                Size(4032, 3024),
+                Size(1920, 1440),
+                Size(1280, 960),
+                Size(640, 480),
+                Size(320, 240),
+                // Mismatched maximum JPEG resolution AspectRatio items, sorted by area size.
+                Size(3840, 2160),
+                Size(1920, 1080),
+                Size(1280, 720),
+                Size(960, 544),
+                Size(800, 450),
+                Size(320, 180),
+                Size(256, 144)
+            )
+        } else {
+            listOf(
+                // Matched preferred AspectRatio items, sorted by area size.
+                Size(3840, 2160),
+                Size(1920, 1080),
+                Size(1280, 720),
+                Size(960, 544),
+                Size(800, 450),
+                Size(320, 180),
+                Size(256, 144),
+                // Mismatched preferred AspectRatio items, sorted by area size.
+                Size(4032, 3024),
+                Size(1920, 1440),
+                Size(1280, 960),
+                Size(640, 480),
+                Size(320, 240)
+            )
+        }
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_preferredResolution1920x1080_InLimitedDevice() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredResolution = Size(1920, 1080),
+            sizeCoordinate = sizeCoordinate
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        // The 4:3 default aspect ratio will make sizes of 4/3 have the 2nd highest priority just
+        // after the preferred resolution.
+        val expectedList =
+            listOf(
+                // Matched preferred resolution size will be put in first priority.
+                Size(1920, 1080),
+                // Matched default preferred AspectRatio items, sorted by area size.
+                Size(1920, 1440),
+                Size(1280, 960),
+                Size(640, 480),
+                Size(320, 240),
+                // Mismatched default preferred AspectRatio items, sorted by area size.
+                Size(1280, 720),
+                Size(960, 544),
+                Size(800, 450),
+                Size(320, 180),
+                Size(256, 144),
+            )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_preferredResolution1920x1080_InLegacyDevice() {
+        setupCameraAndInitCameraX()
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredResolution = Size(1920, 1080),
+            sizeCoordinate = sizeCoordinate
+        )
+
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        // The 4:3 default aspect ratio will make sizes of 4/3 have the 2nd highest priority just
+        // after the preferred resolution.
+        val expectedList = if (Build.VERSION.SDK_INT == 21) {
+                listOf(
+                    // Matched maximum JPEG resolution AspectRatio items, sorted by area size.
+                    Size(1920, 1440),
+                    Size(1280, 960),
+                    Size(640, 480),
+                    Size(320, 240),
+                    // Mismatched maximum JPEG resolution AspectRatio items, sorted by area size.
+                    Size(1920, 1080),
+                    Size(1280, 720),
+                    Size(960, 544),
+                    Size(800, 450),
+                    Size(320, 180),
+                    Size(256, 144)
+                )
+            } else {
+                // The 4:3 default aspect ratio will make sizes of 4/3 have the 2nd highest
+                // priority just after the preferred resolution size.
+                listOf(
+                    // Matched default preferred resolution size will be put in first priority.
+                    Size(1920, 1080),
+                    // Matched preferred AspectRatio items, sorted by area size.
+                    Size(1920, 1440),
+                    Size(1280, 960),
+                    Size(640, 480),
+                    Size(320, 240),
+                    // Mismatched preferred default AspectRatio items, sorted by area size.
+                    Size(1280, 720),
+                    Size(960, 544),
+                    Size(800, 450),
+                    Size(320, 180),
+                    Size(256, 144),
+                )
+            }
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    @Suppress("DEPRECATION") /* defaultDisplay */
+    fun getSupportedOutputSizes_smallDisplay_withMaxResolution1920x1080() {
+        // Sets up small display.
+        val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+        Shadows.shadowOf(windowManager.defaultDisplay).setRealWidth(240)
+        Shadows.shadowOf(windowManager.defaultDisplay).setRealHeight(320)
+        displayInfoManager = DisplayInfoManager.getInstance(context)
+
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            PREVIEW_USE_CASE,
+            preferredAspectRatio = AspectRatio.RATIO_16_9,
+            maxResolution = Size(1920, 1080)
+        )
+        // Max resolution setting will remove sizes larger than 1920x1080. The auto-resolution
+        // mechanism will try to select the sizes which aspect ratio is nearest to the aspect ratio
+        // of target resolution in priority. Therefore, sizes of aspect ratio 16/9 will be in front
+        // of the returned sizes list and the list is sorted in descending order. Other items will
+        // be put in the following that are sorted by aspect ratio delta and then area size.
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // Matched preferred AspectRatio items, sorted by area size.
+            Size(1920, 1080),
+            Size(1280, 720),
+            Size(960, 544),
+            Size(800, 450),
+            Size(320, 180),
+            Size(256, 144),
+
+            // Mismatched preferred AspectRatio items, sorted by area size.
+            Size(1280, 960),
+            Size(640, 480),
+            Size(320, 240)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_preferredResolution1800x1440NearTo4x3() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredResolution = Size(1800, 1440),
+            sizeCoordinate = sizeCoordinate
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList =
+            listOf(
+                // No matched preferred resolution size found.
+                // Matched default preferred AspectRatio items, sorted by area size.
+                Size(1920, 1440),
+                Size(1280, 960),
+                Size(640, 480),
+                Size(320, 240),
+                // Mismatched default preferred AspectRatio items, sorted by area size.
+                Size(3840, 2160),
+                Size(1920, 1080),
+                Size(1280, 720),
+                Size(960, 544),
+                Size(800, 450),
+                Size(320, 180),
+                Size(256, 144)
+            )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_preferredResolution1280x600NearTo16x9() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredResolution = Size(1280, 600),
+            sizeCoordinate = sizeCoordinate
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // No matched preferred resolution size found.
+            // Matched default preferred AspectRatio items, sorted by area size.
+            Size(1280, 960),
+            Size(640, 480),
+            Size(320, 240),
+            // Mismatched default preferred AspectRatio items, sorted by area size.
+            Size(1280, 720),
+            Size(960, 544),
+            Size(800, 450),
+            Size(320, 180),
+            Size(256, 144)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_maxResolution1280x720() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            maxResolution = Size(1280, 720)
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // Matched default preferred AspectRatio items, sorted by area size.
+            Size(640, 480),
+            Size(320, 240),
+            // Mismatched default preferred AspectRatio items, sorted by area size.
+            Size(1280, 720),
+            Size(960, 544),
+            Size(800, 450),
+            Size(320, 180),
+            Size(256, 144)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_maxResolution720x1280() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            maxResolution = Size(720, 1280)
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // Matched default preferred AspectRatio items, sorted by area size.
+            Size(640, 480),
+            Size(320, 240),
+            // Mismatched default preferred AspectRatio items, sorted by area size.
+            Size(320, 180),
+            Size(256, 144)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_defaultResolution1280x720_noTargetResolution() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            defaultResolution = Size(1280, 720)
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // Matched default preferred AspectRatio items, sorted by area size.
+            Size(1280, 960),
+            Size(640, 480),
+            Size(320, 240),
+            // Mismatched default preferred AspectRatio items, sorted by area size.
+            Size(1280, 720),
+            Size(960, 544),
+            Size(800, 450),
+            Size(320, 180),
+            Size(256, 144)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_defaultResolution1280x720_preferredResolution1920x1080() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            defaultResolution = Size(1280, 720),
+            preferredResolution = Size(1920, 1080),
+            sizeCoordinate = sizeCoordinate
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // Matched preferred resolution size will be put in first priority.
+            Size(1920, 1080),
+            // Matched default preferred AspectRatio items, sorted by area size.
+            Size(1920, 1440),
+            Size(1280, 960),
+            Size(640, 480),
+            Size(320, 240),
+            // Mismatched default preferred AspectRatio items, sorted by area size.
+            Size(1280, 720),
+            Size(960, 544),
+            Size(800, 450),
+            Size(320, 180),
+            Size(256, 144)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_fallbackToGuaranteedResolution_whenNotFulfillConditions() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            supportedSizes = arrayOf(
+                Size(640, 480),
+                Size(320, 240),
+                Size(320, 180),
+                Size(256, 144)
+            )
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredResolution = Size(1920, 1080),
+            sizeCoordinate = sizeCoordinate
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // No matched preferred resolution size found.
+            // Matched default preferred AspectRatio items, sorted by area size.
+            Size(640, 480),
+            Size(320, 240),
+            // Mismatched default preferred AspectRatio items, sorted by area size.
+            Size(320, 180),
+            Size(256, 144)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_whenMaxSizeSmallerThanSmallTargetResolution() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            supportedSizes = arrayOf(
+                Size(640, 480),
+                Size(320, 240),
+                Size(320, 180),
+                Size(256, 144)
+            )
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredResolution = Size(320, 240),
+            sizeCoordinate = sizeCoordinate,
+            maxResolution = Size(320, 180)
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(Size(320, 180), Size(256, 144))
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_whenMaxSizeSmallerThanBigPreferredResolution() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredResolution = Size(3840, 2160),
+            sizeCoordinate = sizeCoordinate,
+            maxResolution = Size(1920, 1080)
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // No matched preferred resolution size found after filtering by max resolution setting.
+            // Matched default preferred AspectRatio items, sorted by area size.
+            Size(1280, 960),
+            Size(640, 480),
+            Size(320, 240),
+            // Mismatched default preferred AspectRatio items, sorted by area size.
+            Size(1920, 1080),
+            Size(1280, 720),
+            Size(960, 544),
+            Size(800, 450),
+            Size(320, 180),
+            Size(256, 144)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_whenNoSizeBetweenMaxSizeAndPreferredResolution() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            supportedSizes = arrayOf(
+                Size(640, 480),
+                Size(320, 240),
+                Size(320, 180),
+                Size(256, 144)
+            )
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredResolution = Size(320, 190),
+            sizeCoordinate = sizeCoordinate,
+            maxResolution = Size(320, 200)
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(Size(320, 180), Size(256, 144))
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_whenPreferredResolutionSmallerThanAnySize() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            supportedSizes = arrayOf(
+                Size(640, 480),
+                Size(320, 240),
+                Size(320, 180),
+                Size(256, 144)
+            )
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredResolution = Size(192, 144),
+            sizeCoordinate = sizeCoordinate
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(Size(320, 240), Size(256, 144))
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_whenMaxResolutionSmallerThanAnySize() {
+        setupCameraAndInitCameraX(
+            supportedSizes = arrayOf(
+                Size(640, 480),
+                Size(320, 240),
+                Size(320, 180),
+                Size(256, 144)
+            )
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            maxResolution = Size(192, 144)
+        )
+        // All sizes will be filtered out by the max resolution 192x144 setting and an
+        // IllegalArgumentException will be thrown.
+        Assert.assertThrows(IllegalArgumentException::class.java) {
+            getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        }
+    }
+
+    @Test
+    fun getSupportedOutputSizes_whenMod16IsIgnoredForSmallSizes() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            supportedSizes = arrayOf(
+                Size(640, 480),
+                Size(320, 240),
+                Size(320, 180),
+                Size(296, 144),
+                Size(256, 144)
+            )
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredResolution = Size(185, 90),
+            sizeCoordinate = sizeCoordinate
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // No matched preferred resolution size found.
+            // Matched default preferred AspectRatio items, sorted by area size.
+            Size(320, 240),
+            // Mismatched default preferred AspectRatio items, sorted by area size.
+            Size(256, 144),
+            Size(296, 144)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizes_whenOneMod16SizeClosestToTargetResolution() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            supportedSizes = arrayOf(
+                Size(1920, 1080),
+                Size(1440, 1080),
+                Size(1280, 960),
+                Size(1280, 720),
+                Size(864, 480), // This is a 16:9 mod16 size that is closest to 2016x1080
+                Size(768, 432),
+                Size(640, 480),
+                Size(640, 360),
+                Size(480, 360),
+                Size(384, 288)
+            )
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredResolution = Size(1080, 2016),
+            sizeCoordinate = sizeCoordinate
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // No matched preferred resolution size found.
+            // Matched default preferred AspectRatio items, sorted by area size.
+            Size(1440, 1080),
+            Size(1280, 960),
+            Size(640, 480),
+            Size(480, 360),
+            Size(384, 288),
+            // Mismatched default preferred AspectRatio items, sorted by area size.
+            Size(1920, 1080),
+            Size(1280, 720),
+            Size(864, 480),
+            Size(768, 432),
+            Size(640, 360)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizesWithPortraitPixelArraySize_aspectRatio16x9() {
+        // Sets the sensor orientation as 0 and pixel array size as a portrait size to simulate a
+        // phone device which majorly supports portrait output sizes.
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            sensorOrientation = SENSOR_ORIENTATION_0,
+            pixelArraySize = PORTRAIT_PIXEL_ARRAY_SIZE,
+            supportedSizes = arrayOf(
+                Size(1080, 1920),
+                Size(1080, 1440),
+                Size(960, 1280),
+                Size(720, 1280),
+                Size(960, 540),
+                Size(480, 640),
+                Size(640, 480),
+                Size(360, 480)
+            )
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredAspectRatio = AspectRatio.RATIO_16_9
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // Matched preferred AspectRatio items, sorted by area size.
+            Size(1080, 1920),
+            Size(720, 1280),
+            // Mismatched preferred AspectRatio items, sorted by area size.
+            Size(1080, 1440),
+            Size(960, 1280),
+            Size(480, 640),
+            Size(360, 480),
+            Size(640, 480),
+            Size(960, 540)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizesOnTabletWithPortraitPixelArraySize_aspectRatio16x9() {
+        // Sets the sensor orientation as 90 and pixel array size as a portrait size to simulate a
+        // tablet device which majorly supports portrait output sizes.
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            sensorOrientation = SENSOR_ORIENTATION_90,
+            pixelArraySize = PORTRAIT_PIXEL_ARRAY_SIZE,
+            supportedSizes = arrayOf(
+                Size(1080, 1920),
+                Size(1080, 1440),
+                Size(960, 1280),
+                Size(720, 1280),
+                Size(960, 540),
+                Size(480, 640),
+                Size(640, 480),
+                Size(360, 480)
+            )
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredAspectRatio = AspectRatio.RATIO_16_9
+        )
+        // Due to the pixel array size is portrait, sizes of aspect ratio 9/16 will be in front of
+        // the returned sizes list and the list is sorted in descending order. Other items will be
+        // put in the following that are sorted by aspect ratio delta and then area size.
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // Matched preferred AspectRatio items, sorted by area size.
+            Size(1080, 1920),
+            Size(720, 1280),
+            // Mismatched preferred AspectRatio items, sorted by aspect ratio delta then area size.
+            Size(1080, 1440),
+            Size(960, 1280),
+            Size(480, 640),
+            Size(360, 480),
+            Size(640, 480),
+            Size(960, 540)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizesOnTablet_aspectRatio16x9() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            sensorOrientation = SENSOR_ORIENTATION_0,
+            pixelArraySize = LANDSCAPE_PIXEL_ARRAY_SIZE
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredAspectRatio = AspectRatio.RATIO_16_9
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // Matched preferred AspectRatio items, sorted by area size.
+            Size(3840, 2160),
+            Size(1920, 1080),
+            Size(1280, 720),
+            Size(960, 544),
+            Size(800, 450),
+            Size(320, 180),
+            Size(256, 144),
+            // Mismatched preferred AspectRatio items, sorted by area size.
+            Size(4032, 3024),
+            Size(1920, 1440),
+            Size(1280, 960),
+            Size(640, 480),
+            Size(320, 240)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Test
+    fun getSupportedOutputSizesOnTabletWithPortraitSizes_aspectRatio16x9() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            sensorOrientation = SENSOR_ORIENTATION_0, supportedSizes = arrayOf(
+                Size(1920, 1080),
+                Size(1440, 1080),
+                Size(1280, 960),
+                Size(1280, 720),
+                Size(540, 960),
+                Size(640, 480),
+                Size(480, 640),
+                Size(480, 360)
+            )
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredAspectRatio = AspectRatio.RATIO_16_9
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // Matched preferred AspectRatio items, sorted by area size.
+            Size(1920, 1080),
+            Size(1280, 720),
+            // Mismatched preferred AspectRatio items, sorted by area size.
+            Size(1440, 1080),
+            Size(1280, 960),
+            Size(640, 480),
+            Size(480, 360),
+            Size(480, 640),
+            Size(540, 960)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun getSupportedOutputSizes_whenHighResolutionIsEnabled_aspectRatio16x9() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            capabilities = intArrayOf(
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE
+            ),
+            supportedHighResolutionSizes = arrayOf(Size(8000, 6000), Size(8000, 4500))
+        )
+        val supportedOutputSizesCollector = SupportedOutputSizesCollector(
+            DEFAULT_CAMERA_ID,
+            cameraCharacteristicsCompat,
+            displayInfoManager
+        )
+
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredAspectRatio = AspectRatio.RATIO_16_9,
+            highResolutionEnabled = true
+        )
+        val resultList = getSupportedOutputSizes(supportedOutputSizesCollector, useCase)
+        val expectedList = listOf(
+            // Matched preferred AspectRatio items, sorted by area size.
+            Size(8000, 4500),
+            Size(3840, 2160),
+            Size(1920, 1080),
+            Size(1280, 720),
+            Size(960, 544),
+            Size(800, 450),
+            Size(320, 180),
+            Size(256, 144),
+            // Mismatched preferred AspectRatio items, sorted by area size.
+            Size(8000, 6000),
+            Size(4032, 3024),
+            Size(1920, 1440),
+            Size(1280, 960),
+            Size(640, 480),
+            Size(320, 240)
+        )
+        assertThat(resultList).isEqualTo(expectedList)
+    }
+
+    /**
+     * Sets up camera according to the specified settings and initialize [CameraX].
+     *
+     * @param cameraId the camera id to be set up. Default value is [DEFAULT_CAMERA_ID].
+     * @param hardwareLevel the hardware level of the camera. Default value is
+     * [CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY].
+     * @param sensorOrientation the sensor orientation of the camera. Default value is
+     * [SENSOR_ORIENTATION_90].
+     * @param pixelArraySize the active pixel array size of the camera. Default value is
+     * [LANDSCAPE_PIXEL_ARRAY_SIZE].
+     * @param supportedSizes the supported sizes of the camera. Default value is
+     * [DEFAULT_SUPPORTED_SIZES].
+     * @param capabilities the capabilities of the camera. Default value is null.
+     */
+    private fun setupCameraAndInitCameraX(
+        cameraId: String = DEFAULT_CAMERA_ID,
+        hardwareLevel: Int = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
+        sensorOrientation: Int = SENSOR_ORIENTATION_90,
+        pixelArraySize: Size = LANDSCAPE_PIXEL_ARRAY_SIZE,
+        supportedSizes: Array<Size> = DEFAULT_SUPPORTED_SIZES,
+        supportedHighResolutionSizes: Array<Size>? = null,
+        capabilities: IntArray? = null
+    ) {
+        setupCamera(
+            cameraId,
+            hardwareLevel,
+            sensorOrientation,
+            pixelArraySize,
+            supportedSizes,
+            supportedHighResolutionSizes,
+            capabilities
+        )
+
+        @CameraSelector.LensFacing val lensFacingEnum = CameraUtil.getLensFacingEnumFromInt(
+            CameraCharacteristics.LENS_FACING_BACK
+        )
+        cameraManagerCompat = CameraManagerCompat.from(context)
+        val cameraInfo = FakeCameraInfoInternal(
+            cameraId,
+            sensorOrientation,
+            CameraCharacteristics.LENS_FACING_BACK
+        )
+
+        cameraFactory = FakeCameraFactory().apply {
+            insertCamera(lensFacingEnum, cameraId) {
+                FakeCamera(cameraId, null, cameraInfo)
+            }
+        }
+
+        cameraCharacteristicsCompat = cameraManagerCompat.getCameraCharacteristicsCompat(cameraId)
+
+        initCameraX()
+    }
+
+    /**
+     * Initializes the [CameraX].
+     */
+    private fun initCameraX() {
+        val surfaceManagerProvider =
+            CameraDeviceSurfaceManager.Provider { context, _, availableCameraIds ->
+                Camera2DeviceSurfaceManager(
+                    context,
+                    mockCamcorderProfileHelper,
+                    CameraManagerCompat.from(this@SupportedOutputSizesCollectorTest.context),
+                    availableCameraIds
+                )
+            }
+        val cameraXConfig = CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
+            .setDeviceSurfaceManagerProvider(surfaceManagerProvider)
+            .setCameraFactoryProvider { _, _, _ -> cameraFactory!! }
+            .build()
+        val cameraX: CameraX = try {
+            CameraXUtil.getOrCreateInstance(context) { cameraXConfig }.get()
+        } catch (e: ExecutionException) {
+            throw IllegalStateException("Unable to initialize CameraX for test.")
+        } catch (e: InterruptedException) {
+            throw IllegalStateException("Unable to initialize CameraX for test.")
+        }
+        useCaseConfigFactory = cameraX.defaultConfigFactory
+    }
+
+    /**
+     * Gets the supported output sizes by the converted ResolutionSelector use case config which
+     * will also be converted when a use case is bound to the lifecycle.
+     */
+    private fun getSupportedOutputSizes(
+        supportedOutputSizesCollector: SupportedOutputSizesCollector,
+        useCase: UseCase,
+        cameraId: String = DEFAULT_CAMERA_ID,
+        sensorOrientation: Int = SENSOR_ORIENTATION_90,
+        useCaseConfigFactory: UseCaseConfigFactory = this.useCaseConfigFactory!!
+    ): List<Size?> {
+        // Converts the use case config to new ResolutionSelector config
+        val useCaseToConfigMap = Configs.useCaseConfigMapWithDefaultSettingsFromUseCaseList(
+            cameraFactory!!.getCamera(cameraId).cameraInfoInternal,
+            listOf(useCase),
+            useCaseConfigFactory
+        )
+
+        val useCaseConfig = useCaseToConfigMap[useCase]!!
+        val resolutionSelector = (useCaseConfig as ImageOutputConfig).resolutionSelector
+        val imageFormat = useCaseConfig.inputFormat
+        val customizedSupportSizes = getCustomizedSupportSizesFromConfig(imageFormat, useCaseConfig)
+        val miniBoundingSize = SupportedOutputSizesCollector.getTargetSizeByResolutionSelector(
+            resolutionSelector,
+            Surface.ROTATION_0,
+            sensorOrientation,
+            CameraCharacteristics.LENS_FACING_BACK
+        ) ?: useCaseConfig.getDefaultResolution(null)
+
+        return supportedOutputSizesCollector.getSupportedOutputSizes(
+            resolutionSelector,
+            imageFormat,
+            miniBoundingSize,
+            customizedSupportSizes
+        )
+    }
+
+    companion object {
+
+        /**
+         * Sets up camera according to the specified settings.
+         *
+         * @param cameraId the camera id to be set up. Default value is [DEFAULT_CAMERA_ID].
+         * @param hardwareLevel the hardware level of the camera. Default value is
+         * [CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY].
+         * @param sensorOrientation the sensor orientation of the camera. Default value is
+         * [SENSOR_ORIENTATION_90].
+         * @param pixelArraySize the active pixel array size of the camera. Default value is
+         * [LANDSCAPE_PIXEL_ARRAY_SIZE].
+         * @param supportedSizes the supported sizes of the camera. Default value is
+         * [DEFAULT_SUPPORTED_SIZES].
+         * @param capabilities the capabilities of the camera. Default value is null.
+         */
+        @JvmStatic
+        fun setupCamera(
+            cameraId: String = DEFAULT_CAMERA_ID,
+            hardwareLevel: Int = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
+            sensorOrientation: Int = SENSOR_ORIENTATION_90,
+            pixelArraySize: Size = LANDSCAPE_PIXEL_ARRAY_SIZE,
+            supportedSizes: Array<Size> = DEFAULT_SUPPORTED_SIZES,
+            supportedHighResolutionSizes: Array<Size>? = null,
+            capabilities: IntArray? = null
+        ) {
+            val mockMap = Mockito.mock(StreamConfigurationMap::class.java).also {
+                // Sets up the supported sizes
+                Mockito.`when`(it.getOutputSizes(ArgumentMatchers.anyInt()))
+                    .thenReturn(supportedSizes)
+                // ImageFormat.PRIVATE was supported since API level 23. Before that, the supported
+                // output sizes need to be retrieved via SurfaceTexture.class.
+                Mockito.`when`(it.getOutputSizes(SurfaceTexture::class.java))
+                    .thenReturn(supportedSizes)
+                // This is setup for the test to determine RECORD size from StreamConfigurationMap
+                Mockito.`when`(it.getOutputSizes(MediaRecorder::class.java))
+                    .thenReturn(supportedSizes)
+
+                // Sets up the supported high resolution sizes
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                    Mockito.`when`(it.getHighResolutionOutputSizes(ArgumentMatchers.anyInt()))
+                        .thenReturn(supportedHighResolutionSizes)
+                }
+            }
+
+            val characteristics = ShadowCameraCharacteristics.newCameraCharacteristics()
+            Shadow.extract<ShadowCameraCharacteristics>(characteristics).apply {
+                set(CameraCharacteristics.LENS_FACING, CameraCharacteristics.LENS_FACING_BACK)
+                set(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, hardwareLevel)
+                set(CameraCharacteristics.SENSOR_ORIENTATION, sensorOrientation)
+                set(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE, pixelArraySize)
+                set(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP, mockMap)
+                capabilities?.let {
+                    set(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES, it)
+                }
+            }
+
+            val cameraManager = ApplicationProvider.getApplicationContext<Context>()
+                .getSystemService(Context.CAMERA_SERVICE) as CameraManager
+            (Shadow.extract<Any>(cameraManager) as ShadowCameraManager)
+                .addCamera(cameraId, characteristics)
+        }
+
+        /**
+         * Creates [Preview], [ImageCapture], [ImageAnalysis], [androidx.camera.core.VideoCapture] or
+         * FakeUseCase by the legacy or new ResolutionSelector API according to the specified settings.
+         *
+         * @param useCaseType Which of [Preview], [ImageCapture], [ImageAnalysis],
+         * [androidx.camera.core.VideoCapture] and FakeUseCase should be created.
+         * @param preferredAspectRatio the target aspect ratio setting. Default is UNKNOWN_ASPECT_RATIO
+         * and no target aspect ratio will be set to the created use case.
+         * @param preferredResolution the preferred resolution setting which should be specified in the
+         * camera sensor coordinate. The resolution will be transformed to set via
+         * [ResolutionSelector.Builder.setPreferredResolutionByViewSize] if size coordinate is
+         * [SizeCoordinate.ANDROID_VIEW]. Default is null.
+         * @param maxResolution the max resolution setting. Default is null.
+         * @param highResolutionEnabled the high resolution setting, Default is false.
+         * @param defaultResolution the default resolution setting. Default is null.
+         * @param supportedResolutions the customized supported resolutions. Default is null.
+         */
+        @JvmStatic
+        fun createUseCaseByResolutionSelector(
+            useCaseType: Int,
+            preferredAspectRatio: Int = UNKNOWN_ASPECT_RATIO,
+            preferredResolution: Size? = null,
+            sizeCoordinate: SizeCoordinate = SizeCoordinate.CAMERA_SENSOR,
+            maxResolution: Size? = null,
+            highResolutionEnabled: Boolean = false,
+            defaultResolution: Size? = null,
+            supportedResolutions: List<Pair<Int, Array<Size>>>? = null
+        ): UseCase {
+            val builder = when (useCaseType) {
+                PREVIEW_USE_CASE -> Preview.Builder()
+                IMAGE_CAPTURE_USE_CASE -> ImageCapture.Builder()
+                IMAGE_ANALYSIS_USE_CASE -> ImageAnalysis.Builder()
+                else -> FakeUseCaseConfig.Builder(UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE)
+            }
+
+            val resolutionSelectorBuilder = ResolutionSelector.Builder()
+
+            if (preferredAspectRatio != UNKNOWN_ASPECT_RATIO) {
+                resolutionSelectorBuilder.setPreferredAspectRatio(preferredAspectRatio)
+            }
+
+            preferredResolution?.let {
+                if (sizeCoordinate == SizeCoordinate.CAMERA_SENSOR) {
+                    resolutionSelectorBuilder.setPreferredResolution(it)
+                } else {
+                    val flippedResolution = Size(
+                        /* width= */ it.height,
+                        /* height= */ it.width
+                    )
+                    resolutionSelectorBuilder.setPreferredResolutionByViewSize(flippedResolution)
+                }
+            }
+
+            maxResolution?.let { resolutionSelectorBuilder.setMaxResolution(it) }
+            resolutionSelectorBuilder.setHighResolutionEnabled(highResolutionEnabled)
+
+            builder.setResolutionSelector(resolutionSelectorBuilder.build())
+
+            defaultResolution?.let { builder.setDefaultResolution(it) }
+            supportedResolutions?.let { builder.setSupportedResolutions(it) }
+            return builder.build()
+        }
+
+        @JvmStatic
+        fun getCustomizedSupportSizesFromConfig(
+            imageFormat: Int,
+            config: ImageOutputConfig
+        ): Array<Size>? {
+            var outputSizes: Array<Size>? = null
+
+            // Try to retrieve customized supported resolutions from config.
+            val formatResolutionsPairList = config.getSupportedResolutions(null)
+            if (formatResolutionsPairList != null) {
+                for (formatResolutionPair in formatResolutionsPairList) {
+                    if (formatResolutionPair.first == imageFormat) {
+                        outputSizes = formatResolutionPair.second
+                        break
+                    }
+                }
+            }
+            return outputSizes
+        }
+
+        @JvmStatic
+        @ParameterizedRobolectricTestRunner.Parameters(name = "sizeCoordinate = {0}")
+        fun data() = listOf(
+            SizeCoordinate.CAMERA_SENSOR,
+            SizeCoordinate.ANDROID_VIEW
+        )
+    }
+}
\ No newline at end of file
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 c16a5b5..096c8f3 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
@@ -17,13 +17,9 @@
 package androidx.camera.camera2.internal
 import android.content.Context
 import android.graphics.ImageFormat
-import android.graphics.SurfaceTexture
 import android.hardware.camera2.CameraCharacteristics
-import android.hardware.camera2.CameraManager
 import android.hardware.camera2.CameraMetadata
-import android.hardware.camera2.params.StreamConfigurationMap
 import android.media.CamcorderProfile
-import android.media.MediaRecorder
 import android.os.Build
 import android.util.Pair
 import android.util.Rational
@@ -32,6 +28,8 @@
 import android.view.WindowManager
 import androidx.annotation.NonNull
 import androidx.camera.camera2.Camera2Config
+import androidx.camera.camera2.internal.SupportedOutputSizesCollectorTest.Companion.createUseCaseByResolutionSelector
+import androidx.camera.camera2.internal.SupportedOutputSizesCollectorTest.Companion.setupCamera
 import androidx.camera.camera2.internal.compat.CameraManagerCompat
 import androidx.camera.core.AspectRatio
 import androidx.camera.core.CameraSelector.LensFacing
@@ -46,6 +44,7 @@
 import androidx.camera.core.impl.CameraDeviceSurfaceManager
 import androidx.camera.core.impl.CameraFactory
 import androidx.camera.core.impl.MutableStateObservable
+import androidx.camera.core.impl.SizeCoordinate
 import androidx.camera.core.impl.SurfaceCombination
 import androidx.camera.core.impl.SurfaceConfig
 import androidx.camera.core.impl.SurfaceConfig.ConfigSize
@@ -53,9 +52,9 @@
 import androidx.camera.core.impl.UseCaseConfig
 import androidx.camera.core.impl.UseCaseConfigFactory
 import androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_4_3
+import androidx.camera.core.impl.utils.AspectRatioUtil.hasMatchingAspectRatio
 import androidx.camera.core.impl.utils.CompareSizesByArea
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
-import androidx.camera.core.impl.utils.AspectRatioUtil.hasMatchingAspectRatio
 import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_VGA
 import androidx.camera.testing.CamcorderProfileUtil
 import androidx.camera.testing.CameraUtil
@@ -93,15 +92,11 @@
 import org.robolectric.Shadows
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
-import org.robolectric.shadow.api.Shadow
-import org.robolectric.shadows.ShadowCameraCharacteristics
-import org.robolectric.shadows.ShadowCameraManager
 
 private const val FAKE_USE_CASE = 0
 private const val PREVIEW_USE_CASE = 1
 private const val IMAGE_CAPTURE_USE_CASE = 2
 private const val IMAGE_ANALYSIS_USE_CASE = 3
-private const val VIDEO_CAPTURE_USE_CASE = 4
 private const val UNKNOWN_ROTATION = -1
 private const val UNKNOWN_ASPECT_RATIO = -1
 private const val DEFAULT_CAMERA_ID = "0"
@@ -124,7 +119,6 @@
     Size(1920, 1080), // 16:9
     Size(1280, 960), // 4:3
     Size(1280, 720), // 16:9
-    Size(1280, 720), // duplicate the size since Nexus 5X emulator has the case.
     Size(960, 544), // a mod16 version of resolution with 16:9 aspect ratio.
     Size(800, 450), // 16:9
     Size(640, 480), // 4:3
@@ -146,22 +140,93 @@
     )
     private var cameraManagerCompat: CameraManagerCompat? = null
     private val profileUhd = CamcorderProfileUtil.createCamcorderProfileProxy(
-        CamcorderProfile.QUALITY_2160P, RECORD_SIZE.getWidth(), RECORD_SIZE.getHeight()
+        CamcorderProfile.QUALITY_2160P, RECORD_SIZE.width, RECORD_SIZE.height
     )
     private val profileFhd = CamcorderProfileUtil.createCamcorderProfileProxy(
         CamcorderProfile.QUALITY_1080P, 1920, 1080
     )
     private val profileHd = CamcorderProfileUtil.createCamcorderProfileProxy(
-        CamcorderProfile.QUALITY_720P, PREVIEW_SIZE.getWidth(), PREVIEW_SIZE.getHeight()
+        CamcorderProfile.QUALITY_720P, PREVIEW_SIZE.width, PREVIEW_SIZE.height
     )
     private val profileSd = CamcorderProfileUtil.createCamcorderProfileProxy(
-        CamcorderProfile.QUALITY_480P, RESOLUTION_VGA.getWidth(),
-        RESOLUTION_VGA.getHeight()
+        CamcorderProfile.QUALITY_480P, RESOLUTION_VGA.width,
+        RESOLUTION_VGA.height
     )
     private val context = ApplicationProvider.getApplicationContext<Context>()
     private var cameraFactory: FakeCameraFactory? = null
     private var useCaseConfigFactory: UseCaseConfigFactory? = null
 
+    private val legacyUseCaseCreator = object : UseCaseCreator {
+        override fun createUseCase(
+            useCaseType: Int,
+            targetRotation: Int,
+            preferredAspectRatio: Int,
+            preferredResolution: Size?,
+            maxResolution: Size?,
+            highResolutionEnabled: Boolean,
+            defaultResolution: Size?,
+            supportedResolutions: List<Pair<Int, Array<Size>>>?
+        ): UseCase {
+            return createUseCaseByLegacyApi(
+                useCaseType,
+                targetRotation,
+                preferredAspectRatio,
+                preferredResolution,
+                maxResolution,
+                defaultResolution,
+                supportedResolutions
+            )
+        }
+    }
+
+    private val resolutionSelectorUseCaseCreator = object : UseCaseCreator {
+        override fun createUseCase(
+            useCaseType: Int,
+            targetRotation: Int,
+            preferredAspectRatio: Int,
+            preferredResolution: Size?,
+            maxResolution: Size?,
+            highResolutionEnabled: Boolean,
+            defaultResolution: Size?,
+            supportedResolutions: List<Pair<Int, Array<Size>>>?
+        ): UseCase {
+            return createUseCaseByResolutionSelector(
+                useCaseType,
+                preferredAspectRatio,
+                preferredResolution,
+                sizeCoordinate = SizeCoordinate.CAMERA_SENSOR,
+                maxResolution,
+                highResolutionEnabled,
+                defaultResolution,
+                supportedResolutions
+            )
+        }
+    }
+
+    private val viewSizeResolutionSelectorUseCaseCreator = object : UseCaseCreator {
+        override fun createUseCase(
+            useCaseType: Int,
+            targetRotation: Int,
+            preferredAspectRatio: Int,
+            preferredResolution: Size?,
+            maxResolution: Size?,
+            highResolutionEnabled: Boolean,
+            defaultResolution: Size?,
+            supportedResolutions: List<Pair<Int, Array<Size>>>?
+        ): UseCase {
+            return createUseCaseByResolutionSelector(
+                useCaseType,
+                preferredAspectRatio,
+                preferredResolution,
+                sizeCoordinate = SizeCoordinate.ANDROID_VIEW,
+                maxResolution,
+                highResolutionEnabled,
+                defaultResolution,
+                supportedResolutions
+            )
+        }
+    }
+
     @Suppress("DEPRECATION") // defaultDisplay
     @Before
     fun setUp() {
@@ -416,13 +481,25 @@
     }
 
     @Test
-    fun checkTargetAspectRatioInLegacyDevice() {
+    fun checkTargetAspectRatioInLegacyDevice_LegacyApi() {
+        checkTargetAspectRatioInLegacyDevice(legacyUseCaseCreator)
+    }
+
+    @Test
+    fun checkTargetAspectRatioInLegacyDevice_ResolutionSelector() {
+        checkTargetAspectRatioInLegacyDevice(resolutionSelectorUseCaseCreator)
+    }
+
+    private fun checkTargetAspectRatioInLegacyDevice(useCaseCreator: UseCaseCreator) {
         setupCameraAndInitCameraX()
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
         val targetAspectRatio = ASPECT_RATIO_16_9
-        val useCase = createUseCase(FAKE_USE_CASE, targetAspectRatio = AspectRatio.RATIO_16_9)
+        val useCase = useCaseCreator.createUseCase(
+            FAKE_USE_CASE,
+            preferredAspectRatio = AspectRatio.RATIO_16_9
+        )
         val maxJpegSize = supportedSurfaceCombination.getMaxOutputSizeByFormat(ImageFormat.JPEG)
         val maxJpegAspectRatio = Rational(maxJpegSize.width, maxJpegSize.height)
         val suggestedResolutionMap = getSuggestedResolutionMap(supportedSurfaceCombination, useCase)
@@ -443,15 +520,30 @@
     }
 
     @Test
-    fun checkResolutionForMixedUseCase_AfterBindToLifecycle_InLegacyDevice() {
+    fun checkResolutionForMixedUseCase_AfterBindToLifecycle_InLegacyDevice_LegacyApi() {
+        checkResolutionForMixedUseCase_AfterBindToLifecycle_InLegacyDevice(legacyUseCaseCreator)
+    }
+
+    @Test
+    fun checkResolutionForMixedUseCase_AfterBindToLifecycle_InLegacyDevice_ResolutionSelector() {
+        checkResolutionForMixedUseCase_AfterBindToLifecycle_InLegacyDevice(
+            resolutionSelectorUseCaseCreator
+        )
+    }
+
+    private fun checkResolutionForMixedUseCase_AfterBindToLifecycle_InLegacyDevice(
+        useCaseCreator: UseCaseCreator
+    ) {
         setupCameraAndInitCameraX()
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
         // The test case make sure the selected result is expected after the regular flow.
         val targetAspectRatio = ASPECT_RATIO_16_9
-        val preview =
-            createUseCase(PREVIEW_USE_CASE, targetAspectRatio = AspectRatio.RATIO_16_9) as Preview
+        val preview = useCaseCreator.createUseCase(
+            PREVIEW_USE_CASE,
+            preferredAspectRatio = AspectRatio.RATIO_16_9
+        ) as Preview
         preview.setSurfaceProvider(
             CameraXExecutors.directExecutor(),
             SurfaceTextureProvider.createSurfaceTextureProvider(
@@ -460,10 +552,14 @@
                 )
             )
         )
-        val imageCapture =
-            createUseCase(IMAGE_CAPTURE_USE_CASE, targetAspectRatio = AspectRatio.RATIO_16_9)
-        val imageAnalysis =
-            createUseCase(IMAGE_ANALYSIS_USE_CASE, targetAspectRatio = AspectRatio.RATIO_16_9)
+        val imageCapture = useCaseCreator.createUseCase(
+            IMAGE_CAPTURE_USE_CASE,
+            preferredAspectRatio = AspectRatio.RATIO_16_9
+        )
+        val imageAnalysis = useCaseCreator.createUseCase(
+            IMAGE_ANALYSIS_USE_CASE,
+            preferredAspectRatio = AspectRatio.RATIO_16_9
+        )
         val maxJpegSize = supportedSurfaceCombination.getMaxOutputSizeByFormat(ImageFormat.JPEG)
         val maxJpegAspectRatio = Rational(maxJpegSize.width, maxJpegSize.height)
         val suggestedResolutionMap = getSuggestedResolutionMap(
@@ -517,14 +613,25 @@
     }
 
     @Test
-    fun checkDefaultAspectRatioAndResolutionForMixedUseCase() {
+    fun checkDefaultAspectRatioAndResolutionForMixedUseCase_LegacyApi() {
+        checkDefaultAspectRatioAndResolutionForMixedUseCase(legacyUseCaseCreator)
+    }
+
+    @Test
+    fun checkDefaultAspectRatioAndResolutionForMixedUseCase_ResolutionSelector() {
+        checkDefaultAspectRatioAndResolutionForMixedUseCase(resolutionSelectorUseCaseCreator)
+    }
+
+    private fun checkDefaultAspectRatioAndResolutionForMixedUseCase(
+        useCaseCreator: UseCaseCreator
+    ) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
         )
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val preview = createUseCase(PREVIEW_USE_CASE) as Preview
+        val preview = useCaseCreator.createUseCase(PREVIEW_USE_CASE) as Preview
         preview.setSurfaceProvider(
             CameraXExecutors.directExecutor(),
             SurfaceTextureProvider.createSurfaceTextureProvider(
@@ -533,8 +640,8 @@
                 )
             )
         )
-        val imageCapture = createUseCase(IMAGE_CAPTURE_USE_CASE)
-        val imageAnalysis = createUseCase(IMAGE_ANALYSIS_USE_CASE)
+        val imageCapture = useCaseCreator.createUseCase(IMAGE_CAPTURE_USE_CASE)
+        val imageAnalysis = useCaseCreator.createUseCase(IMAGE_ANALYSIS_USE_CASE)
 
         // Preview/ImageCapture/ImageAnalysis' default config settings that will be applied after
         // bound to lifecycle. Calling bindToLifecycle here to make sure sizes matching to
@@ -577,8 +684,10 @@
         */
         val displayWidth = 1080
         val displayHeight = 2220
-        val preview =
-            createUseCase(PREVIEW_USE_CASE, targetResolution = Size(displayHeight, displayWidth))
+        val preview = createUseCaseByLegacyApi(
+            PREVIEW_USE_CASE,
+            targetResolution = Size(displayHeight, displayWidth)
+        )
         val suggestedResolutionMap = getSuggestedResolutionMap(supportedSurfaceCombination, preview)
         // Checks the preconditions.
         val preconditionSize = Size(256, 144)
@@ -593,7 +702,21 @@
     }
 
     @Test
-    fun checkAspectRatioMatchedSizeCanBeSelected() {
+    fun checkAllSupportedSizesCanBeSelected_LegacyApi() {
+        checkAllSupportedSizesCanBeSelected(legacyUseCaseCreator)
+    }
+
+    @Test
+    fun checkAllSupportedSizesCanBeSelected_ResolutionSelector_SensorSize() {
+        checkAllSupportedSizesCanBeSelected(resolutionSelectorUseCaseCreator)
+    }
+
+    @Test
+    fun checkAllSupportedSizesCanBeSelected_ResolutionSelector_ViewSize() {
+        checkAllSupportedSizesCanBeSelected(viewSizeResolutionSelectorUseCaseCreator)
+    }
+
+    private fun checkAllSupportedSizesCanBeSelected(useCaseCreator: UseCaseCreator) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
         )
@@ -605,10 +728,10 @@
         // will be selected as the result. This test can also verify that size smaller than
         // 640x480 can be selected after set as target resolution.
         DEFAULT_SUPPORTED_SIZES.forEach {
-            val imageCapture = createUseCase(
+            val imageCapture = useCaseCreator.createUseCase(
                 IMAGE_CAPTURE_USE_CASE,
                 Surface.ROTATION_90,
-                targetResolution = it
+                preferredResolution = it
             )
             val suggestedResolutionMap =
                 getSuggestedResolutionMap(supportedSurfaceCombination, imageCapture)
@@ -617,7 +740,35 @@
     }
 
     @Test
-    fun checkCorrectAspectRatioNotMatchedSizeCanBeSelected() {
+    fun checkCorrectAspectRatioNotMatchedSizeCanBeSelected_LegacyApi() {
+        // Sets target resolution as 1280x640, all supported resolutions will be put into
+        // aspect ratio not matched list. Then, 1280x720 will be the nearest matched one.
+        // Finally, checks whether 1280x720 is selected or not.
+        checkCorrectAspectRatioNotMatchedSizeCanBeSelected(legacyUseCaseCreator, Size(1280, 720))
+    }
+
+    // 1280x640 is not included in the supported sizes list. So, the smallest size of the
+    // default aspect ratio 4:3 which is 1280x960 will be finally selected.
+    @Test
+    fun checkCorrectAspectRatioNotMatchedSizeCanBeSelected_ResolutionSelector_SensorSize() {
+        checkCorrectAspectRatioNotMatchedSizeCanBeSelected(
+            resolutionSelectorUseCaseCreator,
+            Size(1280, 960)
+        )
+    }
+
+    @Test
+    fun checkCorrectAspectRatioNotMatchedSizeCanBeSelected_ResolutionSelector_ViewSize() {
+        checkCorrectAspectRatioNotMatchedSizeCanBeSelected(
+            viewSizeResolutionSelectorUseCaseCreator,
+            Size(1280, 960)
+        )
+    }
+
+    private fun checkCorrectAspectRatioNotMatchedSizeCanBeSelected(
+        useCaseCreator: UseCaseCreator,
+        expectedResult: Size
+    ) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
         )
@@ -628,65 +779,44 @@
         // ratio not matched list. Then, 1280x720 will be the nearest matched one. Finally,
         // checks whether 1280x720 is selected or not.
         val resolution = Size(1280, 640)
-        val useCase = createUseCase(
+        val useCase = useCaseCreator.createUseCase(
             FAKE_USE_CASE,
             Surface.ROTATION_90,
-            targetResolution = resolution
+            preferredResolution = resolution
         )
         val suggestedResolutionMap = getSuggestedResolutionMap(supportedSurfaceCombination, useCase)
-        assertThat(Size(1280, 720)).isEqualTo(
-            suggestedResolutionMap[useCase]
-        )
+        assertThat(suggestedResolutionMap[useCase]).isEqualTo(expectedResult)
     }
 
     @Test
-    fun legacyVideo_suggestedResolutionsForMixedUseCaseNotSupportedInLegacyDevice() {
+    fun suggestedResolutionsForMixedUseCaseNotSupportedInLegacyDevice_LegacyApi() {
+        suggestedResolutionsForMixedUseCaseNotSupportedInLegacyDevice(legacyUseCaseCreator)
+    }
+
+    @Test
+    fun suggestedResolutionsForMixedUseCaseNotSupportedInLegacyDevice_ResolutionSelector() {
+        suggestedResolutionsForMixedUseCaseNotSupportedInLegacyDevice(
+            resolutionSelectorUseCaseCreator
+        )
+    }
+
+    private fun suggestedResolutionsForMixedUseCaseNotSupportedInLegacyDevice(
+        useCaseCreator: UseCaseCreator
+    ) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
         )
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val imageCapture = createUseCase(
+        val imageCapture = useCaseCreator.createUseCase(
             IMAGE_CAPTURE_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
-        )
-        val videoCapture = createUseCase(
-            VIDEO_CAPTURE_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
-        )
-        val preview = createUseCase(
-            PREVIEW_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
-        )
-        // An IllegalArgumentException will be thrown because a LEGACY level device can't support
-        // ImageCapture + VideoCapture + Preview
-        assertThrows(IllegalArgumentException::class.java) {
-            getSuggestedResolutionMap(
-                supportedSurfaceCombination,
-                imageCapture,
-                videoCapture,
-                preview
-            )
-        }
-    }
-
-    @Test
-    fun suggestedResolutionsForMixedUseCaseNotSupportedInLegacyDevice() {
-        setupCameraAndInitCameraX(
-            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
-        )
-        val supportedSurfaceCombination = SupportedSurfaceCombination(
-            context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
-        )
-        val imageCapture = createUseCase(
-            IMAGE_CAPTURE_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
+            preferredAspectRatio = AspectRatio.RATIO_16_9
         )
         val videoCapture = createVideoCapture()
-        val preview = createUseCase(
+        val preview = useCaseCreator.createUseCase(
             PREVIEW_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
+            preferredAspectRatio = AspectRatio.RATIO_16_9
         )
         // An IllegalArgumentException will be thrown because a LEGACY level device can't support
         // ImageCapture + VideoCapture + Preview
@@ -701,38 +831,20 @@
     }
 
     @Test
-    fun legacyVideo_suggestedResolutionsForCustomizeResolutionsNotSupportedInLegacyDevice() {
-        setupCameraAndInitCameraX(
-            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
-        )
-        val supportedSurfaceCombination = SupportedSurfaceCombination(
-            context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
-        )
-        // Legacy camera only support (PRIV, PREVIEW) + (PRIV, PREVIEW)
-        val videoResolutionsPairs = listOf(
-            Pair.create(ImageFormat.PRIVATE, arrayOf(RECORD_SIZE))
-        )
-        val previewResolutionsPairs = listOf(
-            Pair.create(ImageFormat.PRIVATE, arrayOf(PREVIEW_SIZE))
-        )
-        val videoCapture = createUseCase(
-            VIDEO_CAPTURE_USE_CASE,
-            maxResolution = RECORD_SIZE, // Override the default max resolution in VideoCapture
-            supportedResolutions = videoResolutionsPairs
-        )
-        val preview = createUseCase(
-            PREVIEW_USE_CASE,
-            supportedResolutions = previewResolutionsPairs
-        )
-        // An IllegalArgumentException will be thrown because the VideoCapture requests to only
-        // support a RECORD size but the configuration can't be supported on a LEGACY level device.
-        assertThrows(IllegalArgumentException::class.java) {
-            getSuggestedResolutionMap(supportedSurfaceCombination, videoCapture, preview)
-        }
+    fun suggestedResolutionsForCustomizeResolutionsNotSupportedInLegacyDevice_LegacyApi() {
+        suggestedResolutionsForCustomizeResolutionsNotSupportedInLegacyDevice(legacyUseCaseCreator)
     }
 
     @Test
-    fun suggestedResolutionsForCustomizeResolutionsNotSupportedInLegacyDevice() {
+    fun suggestedResolutionsForCustomizeResolutionsNotSupportedInLegacyDevice_ResolutionSelector() {
+        suggestedResolutionsForCustomizeResolutionsNotSupportedInLegacyDevice(
+            resolutionSelectorUseCaseCreator
+        )
+    }
+
+    private fun suggestedResolutionsForCustomizeResolutionsNotSupportedInLegacyDevice(
+        useCaseCreator: UseCaseCreator
+    ) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
         )
@@ -744,7 +856,7 @@
             Pair.create(ImageFormat.PRIVATE, arrayOf(PREVIEW_SIZE))
         )
         val videoCapture: VideoCapture<TestVideoOutput> = createVideoCapture(Quality.UHD)
-        val preview = createUseCase(
+        val preview = useCaseCreator.createUseCase(
             PREVIEW_USE_CASE,
             supportedResolutions = previewResolutionsPairs
         )
@@ -756,53 +868,32 @@
     }
 
     @Test
-    fun legacyVideo_getSuggestedResolutionsForMixedUseCaseInLimitedDevice() {
-        setupCameraAndInitCameraX(
-            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
-        )
-        val supportedSurfaceCombination = SupportedSurfaceCombination(
-            context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
-        )
-        val imageCapture = createUseCase(
-            IMAGE_CAPTURE_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
-        )
-        val videoCapture = createUseCase(
-            VIDEO_CAPTURE_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
-        )
-        val preview = createUseCase(
-            PREVIEW_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
-        )
-        val suggestedResolutionMap = getSuggestedResolutionMap(
-            supportedSurfaceCombination,
-            imageCapture,
-            videoCapture,
-            preview
-        )
-        // (PRIV, PREVIEW) + (PRIV, RECORD) + (JPEG, RECORD)
-        assertThat(suggestedResolutionMap[imageCapture]).isEqualTo(RECORD_SIZE)
-        assertThat(suggestedResolutionMap[videoCapture]).isEqualTo(LEGACY_VIDEO_MAXIMUM_SIZE)
-        assertThat(suggestedResolutionMap[preview]).isEqualTo(PREVIEW_SIZE)
+    fun getSuggestedResolutionsForMixedUseCaseInLimitedDevice_LegacyApi() {
+        getSuggestedResolutionsForMixedUseCaseInLimitedDevice(legacyUseCaseCreator)
     }
 
     @Test
-    fun getSuggestedResolutionsForMixedUseCaseInLimitedDevice() {
+    fun getSuggestedResolutionsForMixedUseCaseInLimitedDevice_ResolutionSelector() {
+        getSuggestedResolutionsForMixedUseCaseInLimitedDevice(resolutionSelectorUseCaseCreator)
+    }
+
+    private fun getSuggestedResolutionsForMixedUseCaseInLimitedDevice(
+        useCaseCreator: UseCaseCreator
+    ) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
         )
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val imageCapture = createUseCase(
+        val imageCapture = useCaseCreator.createUseCase(
             IMAGE_CAPTURE_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
+            preferredAspectRatio = AspectRatio.RATIO_16_9
         )
         val videoCapture = createVideoCapture(Quality.HIGHEST)
-        val preview = createUseCase(
+        val preview = useCaseCreator.createUseCase(
             PREVIEW_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
+            preferredAspectRatio = AspectRatio.RATIO_16_9
         )
         val suggestedResolutionMap = getSuggestedResolutionMap(
             supportedSurfaceCombination,
@@ -821,24 +912,38 @@
     // VideoCapture should have higher priority to choose size than ImageCapture.
     @Test
     @Throws(CameraUnavailableException::class)
-    fun getSuggestedResolutionsInFullDevice_videoHasHigherPriorityThanImage() {
+    fun getSuggestedResolutionsInFullDevice_videoHasHigherPriorityThanImage_LegacyApi() {
+        getSuggestedResolutionsInFullDevice_videoHasHigherPriorityThanImage(legacyUseCaseCreator)
+    }
+
+    @Test
+    @Throws(CameraUnavailableException::class)
+    fun getSuggestedResolutionsInFullDevice_videoHasHigherPriorityThanImage_ResolutionSelector() {
+        getSuggestedResolutionsInFullDevice_videoHasHigherPriorityThanImage(
+            resolutionSelectorUseCaseCreator
+        )
+    }
+
+    private fun getSuggestedResolutionsInFullDevice_videoHasHigherPriorityThanImage(
+        useCaseCreator: UseCaseCreator
+    ) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
         )
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val imageCapture = createUseCase(
+        val imageCapture = useCaseCreator.createUseCase(
             IMAGE_CAPTURE_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
+            preferredAspectRatio = AspectRatio.RATIO_16_9
         )
         val videoCapture = createVideoCapture(QualitySelector.from(
             Quality.UHD,
             FallbackStrategy.lowerQualityOrHigherThan(Quality.UHD)
         ))
-        val preview = createUseCase(
+        val preview = useCaseCreator.createUseCase(
             PREVIEW_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
+            preferredAspectRatio = AspectRatio.RATIO_16_9
         )
         val suggestedResolutionMap = getSuggestedResolutionMap(
             supportedSurfaceCombination,
@@ -855,25 +960,38 @@
     }
 
     @Test
-    fun getSuggestedResolutionsInFullDevice_videoRecordSizeLowPriority_imageCanGetMaxSize() {
+    fun imageCaptureCanGetMaxSizeInFullDevice_videoRecordSizeLowPriority_LegacyApi() {
+        imageCaptureCanGetMaxSizeInFullDevice_videoRecordSizeLowPriority(legacyUseCaseCreator)
+    }
+
+    @Test
+    fun imageCaptureCanGetMaxSizeInFullDevice_videoRecordSizeLowPriority_ResolutionSelector() {
+        imageCaptureCanGetMaxSizeInFullDevice_videoRecordSizeLowPriority(
+            resolutionSelectorUseCaseCreator
+        )
+    }
+
+    private fun imageCaptureCanGetMaxSizeInFullDevice_videoRecordSizeLowPriority(
+        useCaseCreator: UseCaseCreator
+    ) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
         )
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val imageCapture = createUseCase(
+        val imageCapture = useCaseCreator.createUseCase(
             IMAGE_CAPTURE_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_4_3 // mMaximumSize(4032x3024) is 4:3
+            preferredAspectRatio = AspectRatio.RATIO_4_3 // mMaximumSize(4032x3024) is 4:3
         )
         val videoCapture = createVideoCapture(
             QualitySelector.fromOrderedList(
-                listOf<androidx.camera.video.Quality>(Quality.HD, Quality.FHD, Quality.UHD)
+                listOf<Quality>(Quality.HD, Quality.FHD, Quality.UHD)
             )
         )
-        val preview = createUseCase(
+        val preview = useCaseCreator.createUseCase(
             PREVIEW_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
+            preferredAspectRatio = AspectRatio.RATIO_16_9
         )
         val suggestedResolutionMap = getSuggestedResolutionMap(
             supportedSurfaceCombination,
@@ -890,9 +1008,50 @@
     }
 
     @Test
-    fun getSuggestedResolutionsWithSameSupportedListForDifferentUseCases() {
+    fun getSuggestedResolutionsWithSameSupportedListForDifferentUseCases_LegacyApi() {
+        getSuggestedResolutionsWithSameSupportedListForDifferentUseCases(
+            legacyUseCaseCreator,
+            DISPLAY_SIZE
+        )
+    }
+
+    @Test
+    fun getSuggestedResolutionsWithSameSupportedListForDifferentUseCases_RS_SensorSize() {
+        getSuggestedResolutionsWithSameSupportedListForDifferentUseCases(
+            resolutionSelectorUseCaseCreator,
+            PREVIEW_SIZE
+        )
+    }
+
+    @Test
+    fun getSuggestedResolutionsWithSameSupportedListForDifferentUseCases_RS_ViewSize() {
+        getSuggestedResolutionsWithSameSupportedListForDifferentUseCases(
+            viewSizeResolutionSelectorUseCaseCreator,
+            PREVIEW_SIZE
+        )
+    }
+
+    private fun getSuggestedResolutionsWithSameSupportedListForDifferentUseCases(
+        useCaseCreator: UseCaseCreator,
+        preferredResolution: Size
+    ) {
         setupCameraAndInitCameraX(
-            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            supportedSizes = arrayOf(
+                Size(4032, 3024), // 4:3
+                Size(3840, 2160), // 16:9
+                Size(1920, 1440), // 4:3
+                Size(1920, 1080), // 16:9
+                Size(1280, 960), // 4:3
+                Size(1280, 720), // 16:9
+                Size(1280, 720), // duplicate the size since Nexus 5X emulator has the case.
+                Size(960, 544), // a mod16 version of resolution with 16:9 aspect ratio.
+                Size(800, 450), // 16:9
+                Size(640, 480), // 4:3
+                Size(320, 240), // 4:3
+                Size(320, 180), // 16:9
+                Size(256, 144) // 16:9 For checkSmallSizesAreFilteredOut test.
+            )
         )
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
@@ -903,9 +1062,18 @@
         2. supportedOutputSizes for ImageCapture and Preview in
         SupportedSurfaceCombination#getAllPossibleSizeArrangements are the same.
         */
-        val imageCapture = createUseCase(IMAGE_CAPTURE_USE_CASE, targetResolution = DISPLAY_SIZE)
-        val preview = createUseCase(PREVIEW_USE_CASE, targetResolution = DISPLAY_SIZE)
-        val imageAnalysis = createUseCase(IMAGE_ANALYSIS_USE_CASE, targetResolution = DISPLAY_SIZE)
+        val imageCapture = useCaseCreator.createUseCase(
+            IMAGE_CAPTURE_USE_CASE,
+            preferredResolution = preferredResolution
+        )
+        val preview = useCaseCreator.createUseCase(
+            PREVIEW_USE_CASE,
+            preferredResolution = preferredResolution
+        )
+        val imageAnalysis = useCaseCreator.createUseCase(
+            IMAGE_ANALYSIS_USE_CASE,
+            preferredResolution = preferredResolution
+        )
         val suggestedResolutionMap = getSuggestedResolutionMap(
             supportedSurfaceCombination,
             imageCapture,
@@ -918,24 +1086,33 @@
     }
 
     @Test
-    fun setTargetAspectRatioForMixedUseCases() {
+    fun setTargetAspectRatioForMixedUseCases_LegacyApi() {
+        setTargetAspectRatioForMixedUseCases(legacyUseCaseCreator)
+    }
+
+    @Test
+    fun setTargetAspectRatioForMixedUseCases_ResolutionSelector() {
+        setTargetAspectRatioForMixedUseCases(resolutionSelectorUseCaseCreator)
+    }
+
+    private fun setTargetAspectRatioForMixedUseCases(useCaseCreator: UseCaseCreator) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
         )
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val preview = createUseCase(
+        val preview = useCaseCreator.createUseCase(
             PREVIEW_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
+            preferredAspectRatio = AspectRatio.RATIO_16_9
         )
-        val imageCapture = createUseCase(
+        val imageCapture = useCaseCreator.createUseCase(
             IMAGE_CAPTURE_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
+            preferredAspectRatio = AspectRatio.RATIO_16_9
         )
-        val imageAnalysis = createUseCase(
+        val imageAnalysis = useCaseCreator.createUseCase(
             IMAGE_ANALYSIS_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9
+            preferredAspectRatio = AspectRatio.RATIO_16_9
         )
         val suggestedResolutionMap = getSuggestedResolutionMap(
             supportedSurfaceCombination,
@@ -964,45 +1141,18 @@
     }
 
     @Test
-    fun legacyVideo_getSuggestedResolutionsForCustomizedSupportedResolutions() {
-        setupCameraAndInitCameraX(
-            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
-        )
-        val supportedSurfaceCombination = SupportedSurfaceCombination(
-            context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
-        )
-        val formatResolutionsPairList = arrayListOf<Pair<Int, Array<Size>>>().apply {
-            add(Pair.create(ImageFormat.JPEG, arrayOf(RESOLUTION_VGA)))
-            add(Pair.create(ImageFormat.YUV_420_888, arrayOf(RESOLUTION_VGA)))
-            add(Pair.create(ImageFormat.PRIVATE, arrayOf(RESOLUTION_VGA)))
-        }
-        // Sets use cases customized supported resolutions to 640x480 only.
-        val imageCapture = createUseCase(
-            IMAGE_CAPTURE_USE_CASE,
-            supportedResolutions = formatResolutionsPairList
-        )
-        val videoCapture = createUseCase(
-            VIDEO_CAPTURE_USE_CASE,
-            supportedResolutions = formatResolutionsPairList
-        )
-        val preview = createUseCase(
-            PREVIEW_USE_CASE,
-            supportedResolutions = formatResolutionsPairList
-        )
-        val suggestedResolutionMap = getSuggestedResolutionMap(
-            supportedSurfaceCombination,
-            imageCapture,
-            videoCapture,
-            preview
-        )
-        // Checks all suggested resolutions will become 640x480.
-        assertThat(suggestedResolutionMap[imageCapture]).isEqualTo(RESOLUTION_VGA)
-        assertThat(suggestedResolutionMap[videoCapture]).isEqualTo(RESOLUTION_VGA)
-        assertThat(suggestedResolutionMap[preview]).isEqualTo(RESOLUTION_VGA)
+    fun getSuggestedResolutionsForCustomizedSupportedResolutions_LegacyApi() {
+        getSuggestedResolutionsForCustomizedSupportedResolutions(legacyUseCaseCreator)
     }
 
     @Test
-    fun getSuggestedResolutionsForCustomizedSupportedResolutions() {
+    fun getSuggestedResolutionsForCustomizedSupportedResolutions_ResolutionSelector() {
+        getSuggestedResolutionsForCustomizedSupportedResolutions(resolutionSelectorUseCaseCreator)
+    }
+
+    private fun getSuggestedResolutionsForCustomizedSupportedResolutions(
+        useCaseCreator: UseCaseCreator
+    ) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
         )
@@ -1015,12 +1165,12 @@
             add(Pair.create(ImageFormat.PRIVATE, arrayOf(RESOLUTION_VGA)))
         }
         // Sets use cases customized supported resolutions to 640x480 only.
-        val imageCapture = createUseCase(
+        val imageCapture = useCaseCreator.createUseCase(
             IMAGE_CAPTURE_USE_CASE,
             supportedResolutions = formatResolutionsPairList
         )
         val videoCapture = createVideoCapture(Quality.SD)
-        val preview = createUseCase(
+        val preview = useCaseCreator.createUseCase(
             PREVIEW_USE_CASE,
             supportedResolutions = formatResolutionsPairList
         )
@@ -1154,17 +1304,27 @@
     }
 
     @Test
-    fun isAspectRatioMatchWithSupportedMod16Resolution() {
+    fun isAspectRatioMatchWithSupportedMod16Resolution_LegacyApi() {
+        isAspectRatioMatchWithSupportedMod16Resolution(legacyUseCaseCreator)
+    }
+
+    @Test
+    fun isAspectRatioMatchWithSupportedMod16Resolution_ResolutionSelector() {
+        isAspectRatioMatchWithSupportedMod16Resolution(resolutionSelectorUseCaseCreator)
+    }
+
+    private fun isAspectRatioMatchWithSupportedMod16Resolution(useCaseCreator: UseCaseCreator) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
         )
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = useCaseCreator.createUseCase(
             FAKE_USE_CASE,
-            targetAspectRatio = AspectRatio.RATIO_16_9,
-            defaultResolution = MOD16_SIZE
+            Surface.ROTATION_90,
+            preferredAspectRatio = AspectRatio.RATIO_16_9,
+            preferredResolution = MOD16_SIZE
         )
         val suggestedResolutionMap = getSuggestedResolutionMap(supportedSurfaceCombination, useCase)
         assertThat(suggestedResolutionMap[useCase]).isEqualTo(MOD16_SIZE)
@@ -1196,7 +1356,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(FAKE_USE_CASE)
+        val useCase = createUseCaseByLegacyApi(FAKE_USE_CASE)
         // There is default minimum size 640x480 setting. Sizes smaller than 640x480 will be
         // removed. No any aspect ratio related setting. The returned sizes list will be sorted in
         // descending order.
@@ -1223,7 +1383,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetAspectRatio = AspectRatio.RATIO_4_3
         )
@@ -1249,14 +1409,14 @@
     }
 
     @Test
-    fun getSupportedOutputSizes_aspectRatio16x9() {
+    fun getSupportedOutputSizes_aspectRatio16x9_InLimitedDevice() {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
         )
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetAspectRatio = AspectRatio.RATIO_16_9
         )
@@ -1287,7 +1447,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetAspectRatio = AspectRatio.RATIO_16_9
         )
@@ -1335,14 +1495,14 @@
     }
 
     @Test
-    fun getSupportedOutputSizes_targetResolution1080x1920InRotation0() {
+    fun getSupportedOutputSizes_targetResolution1080x1920InRotation0_InLimitedDevice() {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
         )
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetResolution = Size(1080, 1920)
         )
@@ -1375,7 +1535,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetResolution = Size(1080, 1920)
         )
@@ -1429,7 +1589,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetRotation = Surface.ROTATION_90,
             targetResolution = Size(1280, 960)
@@ -1464,7 +1624,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetRotation = Surface.ROTATION_90,
             targetResolution = Size(320, 240)
@@ -1494,7 +1654,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             maxResolution = Size(320, 240)
         )
@@ -1518,7 +1678,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetRotation = Surface.ROTATION_90,
             targetResolution = Size(1800, 1440)
@@ -1553,7 +1713,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetRotation = Surface.ROTATION_90,
             targetResolution = Size(1280, 600)
@@ -1585,7 +1745,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             maxResolution = Size(1280, 720)
         )
@@ -1598,7 +1758,20 @@
     }
 
     @Test
-    fun previewCanSelectResolutionLargerThanDisplay_withMaxResolution() {
+    fun previewCanSelectResolutionLargerThanDisplay_withMaxResolution_LegacyApi() {
+        previewCanSelectResolutionLargerThanDisplay_withMaxResolution(legacyUseCaseCreator)
+    }
+
+    @Test
+    fun previewCanSelectResolutionLargerThanDisplay_withMaxResolution_ResolutionSelector() {
+        previewCanSelectResolutionLargerThanDisplay_withMaxResolution(
+            resolutionSelectorUseCaseCreator
+        )
+    }
+
+    private fun previewCanSelectResolutionLargerThanDisplay_withMaxResolution(
+        useCaseCreator: UseCaseCreator
+    ) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
         )
@@ -1606,7 +1779,7 @@
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
         // The max resolution is expressed in the sensor coordinate.
-        val useCase = createUseCase(
+        val useCase = useCaseCreator.createUseCase(
             PREVIEW_USE_CASE,
             maxResolution = MAXIMUM_SIZE
         )
@@ -1623,7 +1796,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             defaultResolution = Size(1280, 720)
         )
@@ -1644,7 +1817,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetRotation = Surface.ROTATION_90,
             defaultResolution = Size(1280, 720),
@@ -1685,7 +1858,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetRotation = Surface.ROTATION_90,
             targetResolution = Size(1920, 1080)
@@ -1712,7 +1885,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             maxResolution = Size(320, 240)
         )
@@ -1739,7 +1912,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetRotation = Surface.ROTATION_90,
             targetResolution = Size(320, 240),
@@ -1769,7 +1942,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetRotation = Surface.ROTATION_90,
             targetResolution = Size(320, 180),
@@ -1793,7 +1966,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetRotation = Surface.ROTATION_90,
             targetResolution = Size(3840, 2160),
@@ -1834,7 +2007,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetRotation = Surface.ROTATION_90,
             targetResolution = Size(320, 190),
@@ -1864,7 +2037,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetRotation = Surface.ROTATION_90,
             targetResolution = Size(192, 144)
@@ -1891,7 +2064,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             maxResolution = Size(192, 144)
         )
@@ -1917,7 +2090,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetRotation = Surface.ROTATION_90,
             targetResolution = Size(185, 90)
@@ -1951,7 +2124,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetResolution = Size(1080, 2016)
         )
@@ -1990,7 +2163,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetAspectRatio = AspectRatio.RATIO_16_9
         )
@@ -2036,7 +2209,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetAspectRatio = AspectRatio.RATIO_16_9
         )
@@ -2070,7 +2243,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetAspectRatio = AspectRatio.RATIO_16_9
         )
@@ -2113,7 +2286,7 @@
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val useCase = createUseCase(
+        val useCase = createUseCaseByLegacyApi(
             FAKE_USE_CASE,
             targetAspectRatio = AspectRatio.RATIO_16_9
         )
@@ -2152,7 +2325,21 @@
     }
 
     @Test
-    fun canGet640x480_whenAnotherGroupMatchedInMod16Exists() {
+    fun canGet640x480_whenAnotherGroupMatchedInMod16Exists_LegacyApi() {
+        canGet640x480_whenAnotherGroupMatchedInMod16Exists(legacyUseCaseCreator)
+    }
+
+    @Test
+    fun canGet640x480_whenAnotherGroupMatchedInMod16Exists_RS_SensorSize() {
+        canGet640x480_whenAnotherGroupMatchedInMod16Exists(resolutionSelectorUseCaseCreator)
+    }
+
+    @Test
+    fun canGet640x480_whenAnotherGroupMatchedInMod16Exists_RS_ViewSize() {
+        canGet640x480_whenAnotherGroupMatchedInMod16Exists(viewSizeResolutionSelectorUseCaseCreator)
+    }
+
+    private fun canGet640x480_whenAnotherGroupMatchedInMod16Exists(useCaseCreator: UseCaseCreator) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
             supportedSizes = arrayOf(
@@ -2171,10 +2358,10 @@
         )
         // Sets the target resolution as 640x480 with target rotation as ROTATION_90 because the
         // sensor orientation is 90.
-        val useCase = createUseCase(
+        val useCase = useCaseCreator.createUseCase(
             FAKE_USE_CASE,
             targetRotation = Surface.ROTATION_90,
-            targetResolution = RESOLUTION_VGA
+            preferredResolution = RESOLUTION_VGA
         )
         val suggestedResolutionMap = getSuggestedResolutionMap(supportedSurfaceCombination, useCase)
         // Checks 640x480 is final selected for the use case.
@@ -2182,7 +2369,20 @@
     }
 
     @Test
-    fun canGetSupportedSizeSmallerThan640x480_whenLargerMaxResolutionIsSet() {
+    fun canGetSupportedSizeSmallerThan640x480_whenLargerMaxResolutionIsSet_LegacyApi() {
+        canGetSupportedSizeSmallerThan640x480_whenLargerMaxResolutionIsSet(legacyUseCaseCreator)
+    }
+
+    @Test
+    fun canGetSupportedSizeSmallerThan640x480_whenLargerMaxResolutionIsSet_ResolutionSelector() {
+        canGetSupportedSizeSmallerThan640x480_whenLargerMaxResolutionIsSet(
+            resolutionSelectorUseCaseCreator
+        )
+    }
+
+    private fun canGetSupportedSizeSmallerThan640x480_whenLargerMaxResolutionIsSet(
+        useCaseCreator: UseCaseCreator
+    ) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
             supportedSizes = arrayOf(Size(480, 480))
@@ -2191,7 +2391,7 @@
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
         // Sets the max resolution as 720x1280
-        val useCase = createUseCase(
+        val useCase = useCaseCreator.createUseCase(
             FAKE_USE_CASE,
             maxResolution = DISPLAY_SIZE
         )
@@ -2201,14 +2401,40 @@
     }
 
     @Test
-    fun previewSizeIsSelectedForImageAnalysis_imageCaptureHasNoSetSizeInLimitedDevice() {
+    fun previewSizeIsSelectedForImageAnalysis_withImageCaptureInLimitedDevice_LegacyApi() {
+        previewSizeIsSelectedForImageAnalysis_withImageCaptureInLimitedDevice(
+            legacyUseCaseCreator, PREVIEW_SIZE
+        )
+    }
+
+    // For the ResolutionSelector API, RECORD_SIZE can't be used because it exceeds
+    // PREVIEW_SIZE. Therefore, the logic will fallback to select a 4:3 PREVIEW_SIZE. Then,
+    // 640x480 will be selected.
+    @Test
+    fun previewSizeIsSelectedForImageAnalysis_withImageCaptureInLimitedDevice_RS_SensorSize() {
+        previewSizeIsSelectedForImageAnalysis_withImageCaptureInLimitedDevice(
+            resolutionSelectorUseCaseCreator, RESOLUTION_VGA
+        )
+    }
+
+    @Test
+    fun previewSizeIsSelectedForImageAnalysis_withImageCaptureInLimitedDevice_RS_ViewSize() {
+        previewSizeIsSelectedForImageAnalysis_withImageCaptureInLimitedDevice(
+            viewSizeResolutionSelectorUseCaseCreator, RESOLUTION_VGA
+        )
+    }
+
+    private fun previewSizeIsSelectedForImageAnalysis_withImageCaptureInLimitedDevice(
+        useCaseCreator: UseCaseCreator,
+        expectedResult: Size
+    ) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
         )
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val preview = createUseCase(PREVIEW_USE_CASE) as Preview
+        val preview = useCaseCreator.createUseCase(PREVIEW_USE_CASE) as Preview
         preview.setSurfaceProvider(
             CameraXExecutors.directExecutor(),
             SurfaceTextureProvider.createSurfaceTextureProvider(
@@ -2218,7 +2444,7 @@
             )
         )
         // ImageCapture has no explicit target resolution setting
-        val imageCapture = createUseCase(IMAGE_CAPTURE_USE_CASE)
+        val imageCapture = useCaseCreator.createUseCase(IMAGE_CAPTURE_USE_CASE)
         // A LEGACY-level above device supports the following configuration.
         //     PRIV/PREVIEW + YUV/PREVIEW + JPEG/MAXIMUM
         //
@@ -2228,10 +2454,10 @@
         // Even there is a RECORD size target resolution setting for ImageAnalysis, ImageCapture
         // will still have higher priority to have a MAXIMUM size resolution if the app doesn't
         // explicitly specify a RECORD size target resolution to ImageCapture.
-        val imageAnalysis = createUseCase(
+        val imageAnalysis = useCaseCreator.createUseCase(
             IMAGE_ANALYSIS_USE_CASE,
             targetRotation = Surface.ROTATION_90,
-            targetResolution = RECORD_SIZE
+            preferredResolution = RECORD_SIZE
         )
         val suggestedResolutionMap = getSuggestedResolutionMap(
             supportedSurfaceCombination,
@@ -2239,18 +2465,40 @@
             imageCapture,
             imageAnalysis
         )
-        assertThat(suggestedResolutionMap[imageAnalysis]).isEqualTo(PREVIEW_SIZE)
+        assertThat(suggestedResolutionMap[imageAnalysis]).isEqualTo(expectedResult)
     }
 
     @Test
-    fun recordSizeIsSelectedForImageAnalysis_imageCaptureHasExplicitSizeInLimitedDevice() {
+    fun imageAnalysisSelectRecordSize_imageCaptureHasExplicitSizeInLimitedDevice_LegacyApi() {
+        imageAnalysisSelectRecordSize_imageCaptureHasExplicitSizeInLimitedDevice(
+            legacyUseCaseCreator
+        )
+    }
+
+    @Test
+    fun imageAnalysisSelectRecordSize_imageCaptureHasExplicitSizeInLimitedDevice_RS_SensorSize() {
+        imageAnalysisSelectRecordSize_imageCaptureHasExplicitSizeInLimitedDevice(
+            resolutionSelectorUseCaseCreator
+        )
+    }
+
+    @Test
+    fun imageAnalysisSelectRecordSize_imageCaptureHasExplicitSizeInLimitedDevice_RS_ViewSize() {
+        imageAnalysisSelectRecordSize_imageCaptureHasExplicitSizeInLimitedDevice(
+            viewSizeResolutionSelectorUseCaseCreator
+        )
+    }
+
+    private fun imageAnalysisSelectRecordSize_imageCaptureHasExplicitSizeInLimitedDevice(
+        useCaseCreator: UseCaseCreator
+    ) {
         setupCameraAndInitCameraX(
             hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
         )
         val supportedSurfaceCombination = SupportedSurfaceCombination(
             context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
         )
-        val preview = createUseCase(PREVIEW_USE_CASE) as Preview
+        val preview = useCaseCreator.createUseCase(PREVIEW_USE_CASE) as Preview
         preview.setSurfaceProvider(
             CameraXExecutors.directExecutor(),
             SurfaceTextureProvider.createSurfaceTextureProvider(
@@ -2260,10 +2508,10 @@
             )
         )
         // ImageCapture has no explicit RECORD size target resolution setting
-        val imageCapture = createUseCase(
+        val imageCapture = useCaseCreator.createUseCase(
             IMAGE_CAPTURE_USE_CASE,
             targetRotation = Surface.ROTATION_90,
-            targetResolution = RECORD_SIZE
+            preferredResolution = RECORD_SIZE
         )
         // A LEGACY-level above device supports the following configuration.
         //     PRIV/PREVIEW + YUV/PREVIEW + JPEG/MAXIMUM
@@ -2274,10 +2522,10 @@
         // A RECORD can be selected for ImageAnalysis if the ImageCapture has a explicit RECORD
         // size target resolution setting. It means that the application know the trade-off and
         // the ImageAnalysis has higher priority to get a larger resolution than ImageCapture.
-        val imageAnalysis = createUseCase(
+        val imageAnalysis = useCaseCreator.createUseCase(
             IMAGE_ANALYSIS_USE_CASE,
             targetRotation = Surface.ROTATION_90,
-            targetResolution = RECORD_SIZE
+            preferredResolution = RECORD_SIZE
         )
         val suggestedResolutionMap = getSuggestedResolutionMap(
             supportedSurfaceCombination,
@@ -2288,6 +2536,92 @@
         assertThat(suggestedResolutionMap[imageAnalysis]).isEqualTo(RECORD_SIZE)
     }
 
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun highResolutionIsSelected_whenHighResolutionIsEnabled() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            capabilities = intArrayOf(
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE
+            ),
+            supportedHighResolutionSizes = arrayOf(Size(8000, 6000), Size(8000, 4500))
+        )
+        val supportedSurfaceCombination = SupportedSurfaceCombination(
+            context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+        )
+
+        val useCase = createUseCaseByResolutionSelector(FAKE_USE_CASE, highResolutionEnabled = true)
+        val suggestedResolutionMap = getSuggestedResolutionMap(supportedSurfaceCombination, useCase)
+
+        // Checks 8000x6000 is final selected for the use case.
+        assertThat(suggestedResolutionMap[useCase]).isEqualTo(Size(8000, 6000))
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun highResolutionIsNotSelected_whenHighResolutionIsEnabled_withoutBurstCaptureCapability() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            supportedHighResolutionSizes = arrayOf(Size(8000, 6000), Size(8000, 4500))
+        )
+        val supportedSurfaceCombination = SupportedSurfaceCombination(
+            context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+        )
+
+        val useCase = createUseCaseByResolutionSelector(FAKE_USE_CASE, highResolutionEnabled = true)
+        val suggestedResolutionMap = getSuggestedResolutionMap(supportedSurfaceCombination, useCase)
+
+        // Checks 8000x6000 is final selected for the use case.
+        assertThat(suggestedResolutionMap[useCase]).isEqualTo(Size(4032, 3024))
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun highResolutionIsNotSelected_whenHighResolutionIsNotEnabled_targetResolution8000x6000() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            capabilities = intArrayOf(
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE
+            ),
+            supportedHighResolutionSizes = arrayOf(Size(8000, 6000), Size(8000, 4500))
+        )
+        val supportedSurfaceCombination = SupportedSurfaceCombination(
+            context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+        )
+
+        val useCase =
+            createUseCaseByResolutionSelector(FAKE_USE_CASE, preferredResolution = Size(8000, 6000))
+        val suggestedResolutionMap = getSuggestedResolutionMap(supportedSurfaceCombination, useCase)
+
+        // Checks 8000x6000 is final selected for the use case.
+        assertThat(suggestedResolutionMap[useCase]).isEqualTo(Size(4032, 3024))
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.M)
+    @Test
+    fun highResolutionIsSelected_whenHighResolutionIsEnabled_aspectRatio16x9() {
+        setupCameraAndInitCameraX(
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+            capabilities = intArrayOf(
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE
+            ),
+            supportedHighResolutionSizes = arrayOf(Size(8000, 6000), Size(8000, 4500))
+        )
+        val supportedSurfaceCombination = SupportedSurfaceCombination(
+            context, DEFAULT_CAMERA_ID, cameraManagerCompat!!, mockCamcorderProfileHelper
+        )
+
+        val useCase = createUseCaseByResolutionSelector(
+            FAKE_USE_CASE,
+            preferredAspectRatio = AspectRatio.RATIO_16_9,
+            highResolutionEnabled = true
+        )
+        val suggestedResolutionMap = getSuggestedResolutionMap(supportedSurfaceCombination, useCase)
+
+        // Checks 8000x6000 is final selected for the use case.
+        assertThat(suggestedResolutionMap[useCase]).isEqualTo(Size(8000, 4500))
+    }
+
     /**
      * Sets up camera according to the specified settings and initialize [CameraX].
      *
@@ -2308,33 +2642,18 @@
         sensorOrientation: Int = SENSOR_ORIENTATION_90,
         pixelArraySize: Size = LANDSCAPE_PIXEL_ARRAY_SIZE,
         supportedSizes: Array<Size> = DEFAULT_SUPPORTED_SIZES,
+        supportedHighResolutionSizes: Array<Size>? = null,
         capabilities: IntArray? = null
     ) {
-        val mockMap = Mockito.mock(StreamConfigurationMap::class.java).also {
-            Mockito.`when`(it.getOutputSizes(ArgumentMatchers.anyInt())).thenReturn(supportedSizes)
-            // ImageFormat.PRIVATE was supported since API level 23. Before that, the supported
-            // output sizes need to be retrieved via SurfaceTexture.class.
-            Mockito.`when`(it.getOutputSizes(SurfaceTexture::class.java)).thenReturn(supportedSizes)
-            // This is setup for the test to determine RECORD size from StreamConfigurationMap
-            Mockito.`when`(it.getOutputSizes(MediaRecorder::class.java)).thenReturn(supportedSizes)
-        }
-
-        val characteristics = ShadowCameraCharacteristics.newCameraCharacteristics()
-        Shadow.extract<ShadowCameraCharacteristics>(characteristics).apply {
-            set(CameraCharacteristics.LENS_FACING, CameraCharacteristics.LENS_FACING_BACK)
-            set(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, hardwareLevel)
-            set(CameraCharacteristics.SENSOR_ORIENTATION, sensorOrientation)
-            set(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE, pixelArraySize)
-            set(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP, mockMap)
-            capabilities?.let {
-                set(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES, it)
-            }
-        }
-
-        val cameraManager = ApplicationProvider.getApplicationContext<Context>()
-            .getSystemService(Context.CAMERA_SERVICE) as CameraManager
-        (Shadow.extract<Any>(cameraManager) as ShadowCameraManager)
-            .addCamera(cameraId, characteristics)
+        setupCamera(
+            cameraId,
+            hardwareLevel,
+            sensorOrientation,
+            pixelArraySize,
+            supportedSizes,
+            supportedHighResolutionSizes,
+            capabilities
+        )
 
         @LensFacing val lensFacingEnum = CameraUtil.getLensFacingEnumFromInt(
             CameraCharacteristics.LENS_FACING_BACK
@@ -2482,7 +2801,7 @@
      * @param supportedResolutions the customized supported resolutions. Default is null.
      */
     @Suppress("DEPRECATION")
-    private fun createUseCase(
+    private fun createUseCaseByLegacyApi(
         useCaseType: Int,
         targetRotation: Int = UNKNOWN_ROTATION,
         targetAspectRatio: Int = UNKNOWN_ASPECT_RATIO,
@@ -2495,7 +2814,6 @@
             PREVIEW_USE_CASE -> Preview.Builder()
             IMAGE_CAPTURE_USE_CASE -> ImageCapture.Builder()
             IMAGE_ANALYSIS_USE_CASE -> ImageAnalysis.Builder()
-            VIDEO_CAPTURE_USE_CASE -> androidx.camera.core.VideoCapture.Builder()
             else -> FakeUseCaseConfig.Builder(UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE)
         }
         if (targetRotation != UNKNOWN_ROTATION) {
@@ -2548,4 +2866,17 @@
             this.sourceState = sourceState
         }
     }
+
+    private interface UseCaseCreator {
+        fun createUseCase(
+            useCaseType: Int,
+            targetRotation: Int = UNKNOWN_ROTATION,
+            preferredAspectRatio: Int = UNKNOWN_ASPECT_RATIO,
+            preferredResolution: Size? = null,
+            maxResolution: Size? = null,
+            highResolutionEnabled: Boolean = false,
+            defaultResolution: Size? = null,
+            supportedResolutions: List<Pair<Int, Array<Size>>>? = null,
+        ): UseCase
+    }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
index 3bcb629..b0ba240 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
@@ -264,9 +264,35 @@
                     ? mSubscribedAnalyzer.getDefaultTargetResolution() : null;
         }
 
-        if (analyzerResolution != null
-                && !builder.getUseCaseConfig().containsOption(OPTION_TARGET_RESOLUTION)) {
-            builder.getMutableConfig().insertOption(OPTION_TARGET_RESOLUTION, analyzerResolution);
+        if (analyzerResolution != null) {
+            if (!builder.getMutableConfig().containsOption(OPTION_RESOLUTION_SELECTOR)) {
+                int targetRotation = builder.getMutableConfig().retrieveOption(
+                        OPTION_TARGET_ROTATION, Surface.ROTATION_0);
+                // analyzerResolution is a size in the sensor coordinate system, but the legacy
+                // target resolution setting is in the view coordinate system. Flips the
+                // analyzerResolution according to the sensor rotation degrees.
+                if (cameraInfo.getSensorRotationDegrees(targetRotation) % 180 == 90) {
+                    analyzerResolution = new Size(/* width= */ analyzerResolution.getHeight(),
+                            /* height= */ analyzerResolution.getWidth());
+                }
+
+                if (!builder.getUseCaseConfig().containsOption(OPTION_TARGET_RESOLUTION)) {
+                    builder.getMutableConfig().insertOption(OPTION_TARGET_RESOLUTION,
+                            analyzerResolution);
+                }
+            } else {
+                // Merges analyzerResolution or default resolution to ResolutionSelector.
+                ResolutionSelector resolutionSelector =
+                        builder.getMutableConfig().retrieveOption(OPTION_RESOLUTION_SELECTOR);
+
+                if (resolutionSelector.getPreferredResolution() == null) {
+                    ResolutionSelector.Builder resolutionSelectorBuilder =
+                            ResolutionSelector.Builder.fromSelector(resolutionSelector);
+                    resolutionSelectorBuilder.setPreferredResolution(analyzerResolution);
+                    builder.getMutableConfig().insertOption(OPTION_RESOLUTION_SELECTOR,
+                            resolutionSelectorBuilder.build());
+                }
+            }
         }
 
         return builder.getUseCaseConfig();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index e658b33..7f247de 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -634,6 +634,21 @@
             builder.getMutableConfig().insertOption(OPTION_INPUT_FORMAT,
                     ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE);
         }
+
+        // Merges Preview's default max resolution setting when resolution selector is used
+        ResolutionSelector resolutionSelector =
+                builder.getMutableConfig().retrieveOption(OPTION_RESOLUTION_SELECTOR, null);
+        if (resolutionSelector != null && resolutionSelector.getMaxResolution() == null) {
+            Size maxResolution = builder.getMutableConfig().retrieveOption(OPTION_MAX_RESOLUTION);
+            if (maxResolution != null) {
+                ResolutionSelector.Builder resolutionSelectorBuilder =
+                        ResolutionSelector.Builder.fromSelector(resolutionSelector);
+                resolutionSelectorBuilder.setMaxResolution(maxResolution);
+                builder.getMutableConfig().insertOption(OPTION_RESOLUTION_SELECTOR,
+                        resolutionSelectorBuilder.build());
+            }
+        }
+
         return builder.getUseCaseConfig();
     }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/SizeUtil.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/SizeUtil.java
index 0c8df56..1eb7a4a 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/SizeUtil.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/SizeUtil.java
@@ -19,7 +19,12 @@
 import android.util.Size;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.camera.core.impl.utils.CompareSizesByArea;
+
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Utility class for size related operations.
@@ -40,4 +45,34 @@
     public static int getArea(@NonNull Size size) {
         return size.getWidth() * size.getHeight();
     }
+
+    /**
+     * Returns {@code true} if the source size area is smaller than the target size area.
+     * Otherwise, returns {@code false}.
+     */
+    public static boolean isSmallerByArea(@NonNull Size sourceSize, @NonNull Size targetSize) {
+        return getArea(sourceSize) < getArea(targetSize);
+    }
+
+    /**
+     * Returns {@code true} if any edge of the source size is longer than the corresponding edge of
+     * the target size. Otherwise, returns {@code false}.
+     */
+    public static boolean isLongerInAnyEdge(@NonNull Size sourceSize, @NonNull Size targetSize) {
+        return sourceSize.getWidth() > targetSize.getWidth()
+                || sourceSize.getHeight() > targetSize.getHeight();
+    }
+
+    /**
+     * Returns the size which has the max area in the input size list. Returns null if the input
+     * size list is empty.
+     */
+    @Nullable
+    public static Size getMaxSize(@NonNull List<Size> sizeList) {
+        if (sizeList.isEmpty()) {
+            return null;
+        }
+
+        return Collections.max(sizeList, new CompareSizesByArea());
+    }
 }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
index 4147593..0bc5cd9 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
@@ -77,6 +77,8 @@
 
     private static final Size APP_RESOLUTION = new Size(100, 200);
     private static final Size ANALYZER_RESOLUTION = new Size(300, 400);
+    private static final Size FLIPPED_ANALYZER_RESOLUTION = new Size(400, 300);
+    private static final Size DEFAULT_RESOLUTION = new Size(640, 480);
 
     private static final int QUEUE_DEPTH = 8;
     private static final int IMAGE_TAG = 0;
@@ -141,31 +143,71 @@
     }
 
     @Test
-    public void setAnalyzerWithResolution_doesNotOverridesUseCaseResolution() {
-        assertThat(getMergedAnalyzerResolution(APP_RESOLUTION, ANALYZER_RESOLUTION)).isEqualTo(
+    public void canSetQueueDepth() {
+        assertThat(getMergedImageAnalysisConfig(null, null, QUEUE_DEPTH,
+                false).getImageQueueDepth()).isEqualTo(QUEUE_DEPTH);
+    }
+
+    @Test
+    public void setAnalyzerWithResolution_doesNotOverridesUseCaseResolution_legacyApi() {
+        assertThat(getMergedImageAnalysisConfig(APP_RESOLUTION, ANALYZER_RESOLUTION, -1,
+                false).getTargetResolution()).isEqualTo(APP_RESOLUTION);
+    }
+
+    @Test
+    public void setAnalyzerWithResolution_doesNotOverridesUseCaseResolution_resolutionSelector() {
+        ImageAnalysisConfig config = getMergedImageAnalysisConfig(APP_RESOLUTION,
+                ANALYZER_RESOLUTION, -1, true);
+        assertThat(config.getResolutionSelector().getPreferredResolution()).isEqualTo(
                 APP_RESOLUTION);
     }
 
     @Test
-    public void setAnalyzerWithResolution_usedAsDefaultUseCaseResolution() {
-        assertThat(getMergedAnalyzerResolution(null, ANALYZER_RESOLUTION)).isEqualTo(
+    public void setAnalyzerWithResolution_usedAsDefaultUseCaseResolution_legacyApi() {
+        assertThat(
+                getMergedImageAnalysisConfig(null, ANALYZER_RESOLUTION, -1,
+                        false).getTargetResolution()).isEqualTo(FLIPPED_ANALYZER_RESOLUTION);
+    }
+
+    @Test
+    public void setAnalyzerWithResolution_usedAsDefaultUseCaseResolution_resolutionSelector() {
+        ImageAnalysisConfig config = getMergedImageAnalysisConfig(null,
+                ANALYZER_RESOLUTION, -1, true);
+        assertThat(config.getResolutionSelector().getPreferredResolution()).isEqualTo(
                 ANALYZER_RESOLUTION);
     }
 
     @Test(expected = IllegalArgumentException.class)
-    public void noAppOrAnalyzerResolution_noMergedOption() {
-        getMergedAnalyzerResolution(null, null);
+    public void noAppOrAnalyzerResolution_noMergedOption_legacyApi() {
+        getMergedImageAnalysisConfig(null, null, -1, false).getTargetResolution();
     }
 
     @NonNull
-    private Size getMergedAnalyzerResolution(
+    private ImageAnalysisConfig getMergedImageAnalysisConfig(
             @Nullable Size appResolution,
-            @Nullable Size analyzerResolution) {
-        // Arrange: set up ImageAnalysis with target resolution.
-        ImageAnalysis.Builder builder = new ImageAnalysis.Builder().setImageQueueDepth(QUEUE_DEPTH);
-        if (appResolution != null) {
-            builder.setTargetResolution(appResolution);
+            @Nullable Size analyzerResolution,
+            int queueDepth,
+            boolean useResolutionSelector) {
+        // Arrange: set up ImageAnalysis.
+        ImageAnalysis.Builder builder = new ImageAnalysis.Builder();
+
+        // Sets preferred resolution by ResolutionSelector or legacy API
+        if (useResolutionSelector) {
+            ResolutionSelector.Builder resolutionSelectorBuilder = new ResolutionSelector.Builder();
+            if (appResolution != null) {
+                resolutionSelectorBuilder.setPreferredResolution(appResolution);
+            }
+            builder.setResolutionSelector(resolutionSelectorBuilder.build());
+        } else {
+            if (appResolution != null) {
+                builder.setTargetResolution(appResolution);
+            }
         }
+
+        if (queueDepth >= 0) {
+            builder.setImageQueueDepth(QUEUE_DEPTH);
+        }
+
         mImageAnalysis = builder.build();
         // Analyzer that overrides the resolution.
         ImageAnalysis.Analyzer analyzer = new ImageAnalysis.Analyzer() {
@@ -183,12 +225,16 @@
         // Act: set the analyzer.
         mImageAnalysis.setAnalyzer(mBackgroundExecutor, analyzer);
 
-        // Assert: only the target resolution is overridden.
-        ImageAnalysisConfig mergedConfig = (ImageAnalysisConfig) mImageAnalysis.mergeConfigs(
-                new FakeCameraInfoInternal(), null, null);
+        return (ImageAnalysisConfig) mImageAnalysis.mergeConfigs(
+                new FakeCameraInfoInternal(90, CameraSelector.LENS_FACING_BACK), null,
+                null);
+    }
 
-        assertThat(mergedConfig.getImageQueueDepth()).isEqualTo(QUEUE_DEPTH);
-        return mergedConfig.getTargetResolution();
+    @NonNull
+    private ImageAnalysisConfig createDefaultConfig() {
+        ImageAnalysis.Builder builder = new ImageAnalysis.Builder();
+        builder.setDefaultResolution(DEFAULT_RESOLUTION);
+        return builder.getUseCaseConfig();
     }
 
     @Test