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