public class

SupportedOutputSizesSorter

extends java.lang.Object

 java.lang.Object

↳androidx.camera.core.internal.SupportedOutputSizesSorter

Gradle dependencies

compile group: 'androidx.camera', name: 'camera-core', version: '1.5.0-alpha01'

  • groupId: androidx.camera
  • artifactId: camera-core
  • version: 1.5.0-alpha01

Artifact androidx.camera:camera-core:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)

Overview

The supported output sizes collector to help collect the available resolution candidate list according to the use case config and the following settings in ResolutionSelector:

  • Aspect ratio strategy
  • Resolution strategy
  • Custom resolution filter
  • High resolution enabled flags

Summary

Constructors
publicSupportedOutputSizesSorter(CameraInfoInternal cameraInfoInternal, Size activeArraySize)

Methods
public java.util.List<Size>getSortedSupportedOutputSizes(UseCaseConfig<UseCase> useCaseConfig)

Returns the sorted output sizes according to the use case config.

public static java.util.List<Size>sortSupportedOutputSizesByResolutionSelector(ResolutionSelector resolutionSelector, java.util.List<Size> candidateSizes, Size maxResolution, int targetRotation, Rational fullFovRatio, int sensorOrientation, int lensFacing)

Sorts the resolution candidate list according to the ResolutionSelector API logic.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public SupportedOutputSizesSorter(CameraInfoInternal cameraInfoInternal, Size activeArraySize)

Methods

public java.util.List<Size> getSortedSupportedOutputSizes(UseCaseConfig<UseCase> useCaseConfig)

Returns the sorted output sizes according to the use case config.

If ResolutionSelector is specified in the use case config, the output sizes will be sorted according to the ResolutionSelector setting and logic. Otherwise, the output sizes will be sorted according to the legacy resolution API settings and logic.

public static java.util.List<Size> sortSupportedOutputSizesByResolutionSelector(ResolutionSelector resolutionSelector, java.util.List<Size> candidateSizes, Size maxResolution, int targetRotation, Rational fullFovRatio, int sensorOrientation, int lensFacing)

Sorts the resolution candidate list according to the ResolutionSelector API logic.

  1. Applies the aspect ratio strategy
    • Applies the aspect ratio strategy fallback rule
  2. Applies the resolution strategy
    • Applies the resolution strategy fallback rule
  3. Applies the resolution filter

Parameters:

resolutionSelector: the ResolutionSelector used to sort the candidate sizes.
candidateSizes: the candidate sizes after the high resolution processing, which will be sorted by the rule of ResolutionSelector.
maxResolution: the max resolutions which sizes larger than it will be removed from candidate sizes.
targetRotation: the target rotation to calculate the rotation degrees to the ResolutionFilter.
fullFovRatio: the full FOV's aspect ratio.
sensorOrientation: the sensor orientation of the current camera.
lensFacing: the lens facing of the current camera

Returns:

a size list which has been filtered and sorted by the specified resolution selector settings.

Source

/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.camera.core.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.util.Pair;
import android.util.Rational;
import android.util.Size;
import android.view.Surface;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.AspectRatio;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.Logger;
import androidx.camera.core.impl.CameraInfoInternal;
import androidx.camera.core.impl.ImageOutputConfig;
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.camera.core.internal.utils.SizeUtil;
import androidx.camera.core.resolutionselector.AspectRatioStrategy;
import androidx.camera.core.resolutionselector.ResolutionFilter;
import androidx.camera.core.resolutionselector.ResolutionSelector;
import androidx.camera.core.resolutionselector.ResolutionStrategy;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
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}:
 *
 * <ul>
 *   <li>Aspect ratio strategy
 *   <li>Resolution strategy
 *   <li>Custom resolution filter
 *   <li>High resolution enabled flags
 * </ul>
 */
public class SupportedOutputSizesSorter {
    private static final String TAG = "SupportedOutputSizesCollector";
    private final CameraInfoInternal mCameraInfoInternal;
    private final int mSensorOrientation;
    private final int mLensFacing;
    private final Rational mFullFovRatio;
    private final SupportedOutputSizesSorterLegacy mSupportedOutputSizesSorterLegacy;

    public SupportedOutputSizesSorter(@NonNull CameraInfoInternal cameraInfoInternal,
            @Nullable Size activeArraySize) {
        mCameraInfoInternal = cameraInfoInternal;
        mSensorOrientation = mCameraInfoInternal.getSensorRotationDegrees();
        mLensFacing = mCameraInfoInternal.getLensFacing();
        mFullFovRatio = activeArraySize != null ? calculateFullFovRatioFromActiveArraySize(
                activeArraySize) : calculateFullFovRatioFromSupportedOutputSizes(
                mCameraInfoInternal);
        mSupportedOutputSizesSorterLegacy =
                new SupportedOutputSizesSorterLegacy(cameraInfoInternal, mFullFovRatio);
    }

    /**
     * Calculates the full FOV ratio by the active array size.
     */
    @NonNull
    private Rational calculateFullFovRatioFromActiveArraySize(@NonNull Size activeArraySize) {
        return new Rational(activeArraySize.getWidth(), activeArraySize.getHeight());
    }

    /**
     * Calculates the full FOV ratio by the output sizes retrieved from CameraInfoInternal.
     *
     * <p>For most devices, the full FOV ratio should match the aspect ratio of the max supported
     * output sizes. The active pixel array info is not used because it may cause robolectric
     * test to fail if it is not set in the test environment.
     */
    @Nullable
    private Rational calculateFullFovRatioFromSupportedOutputSizes(
            @NonNull CameraInfoInternal cameraInfoInternal) {
        List<Size> jpegOutputSizes = cameraInfoInternal.getSupportedResolutions(ImageFormat.JPEG);
        if (jpegOutputSizes.isEmpty()) {
            return null;
        }
        Size maxSize = Collections.max(jpegOutputSizes, new CompareSizesByArea());
        return new Rational(maxSize.getWidth(), maxSize.getHeight());
    }

    /**
     * Returns the sorted output sizes according to the use case config.
     *
     * <p>If ResolutionSelector is specified in the use case config, the output sizes will be
     * sorted according to the ResolutionSelector setting and logic. Otherwise, the output sizes
     * will be sorted according to the legacy resolution API settings and logic.
     */
    @NonNull
    public List<Size> getSortedSupportedOutputSizes(@NonNull UseCaseConfig<?> useCaseConfig) {
        ImageOutputConfig imageOutputConfig = (ImageOutputConfig) useCaseConfig;
        List<Size> customOrderedResolutions = imageOutputConfig.getCustomOrderedResolutions(null);

        // Directly returns the custom ordered resolutions list if it is set.
        if (customOrderedResolutions != null) {
            return customOrderedResolutions;
        }

        ResolutionSelector resolutionSelector = imageOutputConfig.getResolutionSelector(null);
        List<Pair<Integer, Size[]>>  customResolutions =
                imageOutputConfig.getSupportedResolutions(null);
        List<Size> candidateSizes = getResolutionCandidateList(customResolutions,
                useCaseConfig.getInputFormat());

        if (resolutionSelector == null) {
            return mSupportedOutputSizesSorterLegacy.sortSupportedOutputSizes(
                    candidateSizes, useCaseConfig);
        } else {
            Size maxResolution = ((ImageOutputConfig) useCaseConfig).getMaxResolution(null);
            int targetRotation = imageOutputConfig.getTargetRotation(Surface.ROTATION_0);
            // Applies the high resolution settings onto the resolution candidate list.
            if (!useCaseConfig.isHighResolutionDisabled(false)) {
                candidateSizes = applyHighResolutionSettings(candidateSizes,
                        resolutionSelector, useCaseConfig.getInputFormat());
            }
            return sortSupportedOutputSizesByResolutionSelector(
                    imageOutputConfig.getResolutionSelector(),
                    candidateSizes,
                    maxResolution,
                    targetRotation,
                    mFullFovRatio,
                    mSensorOrientation,
                    mLensFacing);
        }
    }

    @Nullable
    private List<Size> getSizeListByFormat(
            @Nullable List<Pair<Integer, Size[]>> resolutionsPairList,
            int imageFormat) {
        Size[] outputSizes = null;

        if (resolutionsPairList != null) {
            for (Pair<Integer, Size[]> formatResolutionPair : resolutionsPairList) {
                if (formatResolutionPair.first == imageFormat) {
                    outputSizes = formatResolutionPair.second;
                    break;
                }
            }
        }
        return outputSizes == null ? null : Arrays.asList(outputSizes);
    }

    /**
     * Sorts the resolution candidate list according to the ResolutionSelector API logic.
     *
     * <ol>
     *   <li>Applies the aspect ratio strategy
     *     <ul>
     *       <li>Applies the aspect ratio strategy fallback rule
     *     </ul>
     *   <li>Applies the resolution strategy
     *     <ul>
     *       <li>Applies the resolution strategy fallback rule
     *     </ul>
     *   <li>Applies the resolution filter
     * </ol>
     * @param resolutionSelector the ResolutionSelector used to sort the candidate
     *                           sizes.
     * @param candidateSizes     the candidate sizes after the high resolution processing, which
     *                           will be sorted by the rule of ResolutionSelector.
     * @param maxResolution      the max resolutions which sizes larger than it will be removed
     *                           from candidate sizes.
     * @param targetRotation     the target rotation to calculate the rotation degrees to the
     *                           {@link ResolutionFilter}.
     * @param fullFovRatio       the full FOV's aspect ratio.
     * @param sensorOrientation  the sensor orientation of the current camera.
     * @param lensFacing         the lens facing of the current camera
     * @return a size list which has been filtered and sorted by the specified resolution
     * selector settings.
     * @throws IllegalArgumentException if the specified resolution filter returns any size which
     *                                  is not included in the provided supported size list.
     */
    @NonNull
    public static List<Size> sortSupportedOutputSizesByResolutionSelector(
            @NonNull ResolutionSelector resolutionSelector,
            @NonNull List<Size> candidateSizes,
            @Nullable Size maxResolution,
            int targetRotation,
            @NonNull Rational fullFovRatio,
            int sensorOrientation,
            int lensFacing) {

        // Applies the aspect ratio strategy onto the resolution candidate list.
        LinkedHashMap<Rational, List<Size>> aspectRatioSizeListMap =
                applyAspectRatioStrategy(candidateSizes,
                        resolutionSelector.getAspectRatioStrategy(), fullFovRatio);

        // Applies the max resolution setting
        if (maxResolution != null) {
            applyMaxResolutionRestriction(aspectRatioSizeListMap, maxResolution);
        }

        // Applies the resolution strategy onto the resolution candidate list.
        applyResolutionStrategy(aspectRatioSizeListMap, resolutionSelector.getResolutionStrategy());

        // Collects all sizes from the sorted aspect ratio size groups into the final sorted list.
        List<Size> resultList = new ArrayList<>();
        for (List<Size> sortedSizes : aspectRatioSizeListMap.values()) {
            for (Size size : sortedSizes) {
                // A size may exist in multiple groups in mod16 condition. Keep only one in
                // the final list.
                if (!resultList.contains(size)) {
                    resultList.add(size);
                }
            }
        }

        // Applies the resolution filter onto the resolution candidate list.
        return applyResolutionFilter(resultList, resolutionSelector.getResolutionFilter(),
                targetRotation, sensorOrientation, lensFacing);
    }

    /**
     * Returns the normal supported output sizes.
     *
     * <p>When using camera-camera2 implementation, the output sizes are retrieved via
     * StreamConfigurationMap#getOutputSizes().
     *
     * @return the resolution candidate list sorted in descending order.
     */
    @NonNull
    private List<Size> getResolutionCandidateList(
            @Nullable List<Pair<Integer, Size[]>> customResolutions, int imageFormat) {
        // Tries to get the custom supported resolutions list if it is set
        List<Size> resolutionCandidateList = getSizeListByFormat(customResolutions, imageFormat);

        // Tries to get the supported output sizes from the CameraInfoInternal if both custom
        // ordered and supported resolutions lists are not set.
        if (resolutionCandidateList == null) {
            resolutionCandidateList = mCameraInfoInternal.getSupportedResolutions(imageFormat);
        }

        // CameraInfoInternal.getSupportedResolutions is not guaranteed to return a modifiable list
        // needed by Collections.sort(), so it is converted to a modifiable list here
        resolutionCandidateList = new ArrayList<>(resolutionCandidateList);

        Collections.sort(resolutionCandidateList, new CompareSizesByArea(true));

        if (resolutionCandidateList.isEmpty()) {
            Logger.w(TAG, "The retrieved supported resolutions from camera info internal is empty"
                    + ". Format is " + imageFormat + ".");
        }

        return resolutionCandidateList;
    }

    /**
     * Appends the high resolution supported output sizes according to the high resolution settings.
     *
     * <p>When using camera-camera2 implementation, the output sizes are retrieved via
     * StreamConfigurationMap#getHighResolutionOutputSizes().
     *
     * @param resolutionCandidateList the supported size list which contains only normal output
     *                                sizes.
     * @param resolutionSelector      the specified resolution selector.
     * @param imageFormat             the desired image format for the target use case.
     * @return the resolution candidate list including the high resolution output sizes sorted in
     * descending order.
     */
    @NonNull
    private List<Size> applyHighResolutionSettings(@NonNull List<Size> resolutionCandidateList,
            @NonNull ResolutionSelector resolutionSelector, int imageFormat) {
        // Appends high resolution output sizes if high resolution is enabled by ResolutionSelector
        if (resolutionSelector.getAllowedResolutionMode()
                == ResolutionSelector.PREFER_HIGHER_RESOLUTION_OVER_CAPTURE_RATE) {
            List<Size> allSizesList = new ArrayList<>();
            allSizesList.addAll(resolutionCandidateList);
            allSizesList.addAll(mCameraInfoInternal.getSupportedHighResolutions(imageFormat));
            Collections.sort(allSizesList, new CompareSizesByArea(true));
            return allSizesList;
        }

        return resolutionCandidateList;
    }

    /**
     * Applies the aspect ratio strategy onto the input resolution candidate list.
     *
     * @param resolutionCandidateList the supported sizes list which has been sorted in
     *                                descending order.
     * @param aspectRatioStrategy     the specified aspect ratio strategy.
     * @return an aspect ratio to size list linked hash map which the aspect ratio fallback rule
     * is applied and is sorted against the preferred aspect ratio.
     */
    @NonNull
    private static LinkedHashMap<Rational, List<Size>> applyAspectRatioStrategy(
            @NonNull List<Size> resolutionCandidateList,
            @NonNull AspectRatioStrategy aspectRatioStrategy,
            Rational fullFovRatio) {
        // Group output sizes by aspect ratio.
        Map<Rational, List<Size>> aspectRatioSizeListMap =
                groupSizesByAspectRatio(resolutionCandidateList);

        // Applies the aspect ratio fallback rule
        return applyAspectRatioStrategyFallbackRule(
                aspectRatioSizeListMap, aspectRatioStrategy, fullFovRatio);
    }

    /**
     * Applies the aspect ratio strategy fallback rule to the aspect ratio to size list map.
     *
     * @param sizeGroupsMap       the aspect ratio to size list map. The size list should have been
     *                            sorted in descending order.
     * @param aspectRatioStrategy the specified aspect ratio strategy.
     * @return an aspect ratio to size list linked hash map which the aspect ratio fallback rule
     * is applied and is sorted against the preferred aspect ratio.
     */
    private static LinkedHashMap<Rational, List<Size>> applyAspectRatioStrategyFallbackRule(
            @NonNull Map<Rational, List<Size>> sizeGroupsMap,
            @NonNull AspectRatioStrategy aspectRatioStrategy,
            Rational fullFovRatio) {
        // Determines the sensor resolution orientation info by the full FOV ratio.
        boolean isSensorLandscapeResolution = fullFovRatio != null ? fullFovRatio.getNumerator()
                >= fullFovRatio.getDenominator() : true;
        Rational aspectRatio = getTargetAspectRatioRationalValue(
                aspectRatioStrategy.getPreferredAspectRatio(), isSensorLandscapeResolution);

        // Remove items of all other aspect ratios if the fallback rule is AspectRatioStrategy
        // .FALLBACK_RULE_NONE
        if (aspectRatioStrategy.getFallbackRule() == AspectRatioStrategy.FALLBACK_RULE_NONE) {
            Rational preferredAspectRatio = getTargetAspectRatioRationalValue(
                    aspectRatioStrategy.getPreferredAspectRatio(), isSensorLandscapeResolution);
            for (Rational ratio : new ArrayList<>(sizeGroupsMap.keySet())) {
                if (!ratio.equals(preferredAspectRatio)) {
                    sizeGroupsMap.remove(ratio);
                }
            }
        }

        // Sorts the aspect ratio key set by the preferred aspect ratio.
        List<Rational> aspectRatios = new ArrayList<>(sizeGroupsMap.keySet());
        Collections.sort(aspectRatios,
                new AspectRatioUtil.CompareAspectRatiosByMappingAreaInFullFovAspectRatioSpace(
                        aspectRatio, fullFovRatio));

        // Stores the size groups into LinkedHashMap to keep the order
        LinkedHashMap<Rational, List<Size>> sortedAspectRatioSizeListMap = new LinkedHashMap<>();
        for (Rational ratio : aspectRatios) {
            sortedAspectRatioSizeListMap.put(ratio, sizeGroupsMap.get(ratio));
        }

        return sortedAspectRatioSizeListMap;
    }

    /**
     * Applies the resolution strategy onto the aspect ratio to size list linked hash map.
     *
     * <p>The resolution fallback rule is applied to filter out and sort the sizes in the
     * underlying size list.
     *
     * @param sortedAspectRatioSizeListMap the aspect ratio to size list linked hash map. The
     *                                     entries order should not be changed.
     * @param resolutionStrategy           the resolution strategy to sort the candidate
     *                                     resolutions.
     */
    private static void applyResolutionStrategy(
            @NonNull LinkedHashMap<Rational, List<Size>> sortedAspectRatioSizeListMap,
            @Nullable ResolutionStrategy resolutionStrategy) {
        if (resolutionStrategy == null) {
            return;
        }

        // Applies the resolution strategy with the specified fallback rule
        for (Rational key : sortedAspectRatioSizeListMap.keySet()) {
            applyResolutionStrategyFallbackRule(sortedAspectRatioSizeListMap.get(key),
                    resolutionStrategy);
        }
    }

    /**
     * Applies the resolution strategy fallback rule to the size list.
     *
     * @param supportedSizesList the supported sizes list which has been sorted in descending order.
     * @param resolutionStrategy the resolution strategy to sort the candidate resolutions.
     */
    private static void applyResolutionStrategyFallbackRule(
            @NonNull List<Size> supportedSizesList,
            @NonNull ResolutionStrategy resolutionStrategy) {
        if (supportedSizesList.isEmpty()) {
            return;
        }
        Integer fallbackRule = resolutionStrategy.getFallbackRule();

        if (resolutionStrategy.equals(ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY)) {
            // Do nothing for HIGHEST_AVAILABLE_STRATEGY case.
            return;
        }

        Size boundSize = resolutionStrategy.getBoundSize();

        switch (fallbackRule) {
            case ResolutionStrategy.FALLBACK_RULE_NONE:
                sortSupportedSizesByFallbackRuleNone(supportedSizesList, boundSize);
                break;
            case ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER:
                sortSupportedSizesByFallbackRuleClosestHigherThenLower(supportedSizesList,
                        boundSize, true);
                break;
            case ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER:
                sortSupportedSizesByFallbackRuleClosestHigherThenLower(supportedSizesList,
                        boundSize, false);
                break;
            case ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER:
                sortSupportedSizesByFallbackRuleClosestLowerThenHigher(supportedSizesList,
                        boundSize, true);
                break;
            case ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER:
                sortSupportedSizesByFallbackRuleClosestLowerThenHigher(supportedSizesList,
                        boundSize, false);
                break;
            default:
                break;
        }
    }

    /**
     * Applies the max resolution restriction.
     *
     * <p>Filters out the output sizes that exceed the max resolution in area size.
     *
     * @param sortedAspectRatioSizeListMap the aspect ratio to size list linked hash map. The
     *                                     entries order should not be changed.
     * @param maxResolution                the max resolution size.
     */
    private static void applyMaxResolutionRestriction(
            @NonNull LinkedHashMap<Rational, List<Size>> sortedAspectRatioSizeListMap,
            @NonNull Size maxResolution) {
        int maxResolutionAreaSize = SizeUtil.getArea(maxResolution);
        for (Rational key : sortedAspectRatioSizeListMap.keySet()) {
            List<Size> supportedSizesList = sortedAspectRatioSizeListMap.get(key);
            List<Size> filteredResultList = new ArrayList<>();
            for (Size size : supportedSizesList) {
                if (SizeUtil.getArea(size) <= maxResolutionAreaSize) {
                    filteredResultList.add(size);
                }
            }
            supportedSizesList.clear();
            supportedSizesList.addAll(filteredResultList);
        }
    }

    /**
     * Applies the resolution filtered to the sorted output size list.
     *
     * @param sizeList         the supported size list which has been filtered and sorted by the
     *                         specified aspect ratio, resolution strategies.
     * @param resolutionFilter the specified resolution filter.
     * @param targetRotation   the use case target rotation info
     * @return the result size list applied the specified resolution filter.
     * @throws IllegalArgumentException if the specified resolution filter returns any size which
     *                                  is not included in the provided supported size list.
     */
    @NonNull
    private static List<Size> applyResolutionFilter(@NonNull List<Size> sizeList,
            @Nullable ResolutionFilter resolutionFilter,
            @ImageOutputConfig.RotationValue int targetRotation,
            int sensorOrientation,
            int lensFacing) {
        if (resolutionFilter == null) {
            return sizeList;
        }

        // Invokes ResolutionFilter#filter() to filter/sort and return the result if it is
        // specified.
        int destRotationDegrees = CameraOrientationUtil.surfaceRotationToDegrees(
                targetRotation);
        int rotationDegrees =
                CameraOrientationUtil.getRelativeImageRotation(destRotationDegrees,
                        sensorOrientation,
                        lensFacing == CameraSelector.LENS_FACING_BACK);
        List<Size> filteredResultList = resolutionFilter.filter(new ArrayList<>(sizeList),
                rotationDegrees);
        if (sizeList.containsAll(filteredResultList)) {
            return filteredResultList;
        } else {
            throw new IllegalArgumentException("The returned sizes list of the resolution "
                    + "filter must be a subset of the provided sizes list.");
        }
    }

    /**
     * Sorts the size list for {@link ResolutionStrategy#FALLBACK_RULE_NONE}.
     *
     * @param supportedSizesList the supported sizes list which has been sorted in descending order.
     * @param boundSize          the resolution strategy bound size.
     */
    private static void sortSupportedSizesByFallbackRuleNone(
            @NonNull List<Size> supportedSizesList, @NonNull Size boundSize) {
        boolean containsBoundSize = supportedSizesList.contains(boundSize);
        supportedSizesList.clear();
        if (containsBoundSize) {
            supportedSizesList.add(boundSize);
        }
    }

    /**
     * Sorts the size list for {@link ResolutionStrategy#FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER}
     * or {@link ResolutionStrategy#FALLBACK_RULE_CLOSEST_HIGHER}.
     *
     * @param supportedSizesList the supported sizes list which has been sorted in descending order.
     * @param boundSize          the resolution strategy bound size.
     * @param keepLowerSizes     keeps the sizes lower than the bound size in the result list if
     *                           this is {@code true}.
     */
    static void sortSupportedSizesByFallbackRuleClosestHigherThenLower(
            @NonNull List<Size> supportedSizesList, @NonNull Size boundSize,
            boolean keepLowerSizes) {
        List<Size> lowerSizes = new ArrayList<>();

        for (int i = supportedSizesList.size() - 1; i >= 0; i--) {
            Size outputSize = supportedSizesList.get(i);
            if (outputSize.getWidth() < boundSize.getWidth()
                    || outputSize.getHeight() < boundSize.getHeight()) {
                // The supportedSizesList is in descending order. Checking and put the
                // bounding-below size at position 0 so that the largest smaller resolution
                // will be put in the first position finally.
                lowerSizes.add(0, outputSize);
            } else {
                break;
            }
        }
        // Removes the lower sizes from the list
        supportedSizesList.removeAll(lowerSizes);
        // Reverses the list so that the smallest larger resolution will be put in the first
        // position.
        Collections.reverse(supportedSizesList);
        if (keepLowerSizes) {
            // Appends the lower sizes to the tail
            supportedSizesList.addAll(lowerSizes);
        }
    }

    /**
     * Sorts the size list for {@link ResolutionStrategy#FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER}
     * or {@link ResolutionStrategy#FALLBACK_RULE_CLOSEST_LOWER}.
     *
     * @param supportedSizesList the supported sizes list which has been sorted in descending order.
     * @param boundSize          the resolution strategy bound size.
     * @param keepHigherSizes    keeps the sizes higher than the bound size in the result list if
     *                           this is {@code true}.
     */
    private static void sortSupportedSizesByFallbackRuleClosestLowerThenHigher(
            @NonNull List<Size> supportedSizesList, @NonNull Size boundSize,
            boolean keepHigherSizes) {
        List<Size> higherSizes = new ArrayList<>();

        for (int i = 0; i < supportedSizesList.size(); i++) {
            Size outputSize = supportedSizesList.get(i);
            if (outputSize.getWidth() > boundSize.getWidth()
                    || outputSize.getHeight() > boundSize.getHeight()) {
                // The supportedSizesList is in descending order. Checking and put the
                // bounding-above size at position 0 so that the smallest larger resolution
                // will be put in the first position finally.
                higherSizes.add(0, outputSize);
            } else {
                // Breaks the for-loop to keep the equal-to or lower sizes in the list.
                break;
            }
        }
        // Removes the higher sizes from the list
        supportedSizesList.removeAll(higherSizes);
        if (keepHigherSizes) {
            // Appends the higher sizes to the tail
            supportedSizesList.addAll(higherSizes);
        }
    }

    /**
     * Returns the target aspect ratio rational value according to the ResolutionSelector settings.
     */
    @Nullable
    static Rational getTargetAspectRatioRationalValue(@AspectRatio.Ratio int aspectRatio,
            boolean isSensorLandscapeResolution) {
        Rational outputRatio = null;

        switch (aspectRatio) {
            case AspectRatio.RATIO_4_3:
                outputRatio = isSensorLandscapeResolution ? ASPECT_RATIO_4_3
                        : ASPECT_RATIO_3_4;
                break;
            case AspectRatio.RATIO_16_9:
                outputRatio = isSensorLandscapeResolution ? ASPECT_RATIO_16_9
                        : ASPECT_RATIO_9_16;
                break;
            case AspectRatio.RATIO_DEFAULT:
                break;
            default:
                Logger.e(TAG, "Undefined target aspect ratio: " + aspectRatio);
        }

        return outputRatio;
    }

    /**
     * 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;
    }

    /**
     * Groups the input sizes into an aspect ratio to size list map.
     */
    static Map<Rational, List<Size>> groupSizesByAspectRatio(@NonNull 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;
    }
}