1 /* 2 * Copyright 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.camera.core.internal; 18 19 import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_16_9; 20 import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_3_4; 21 import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_4_3; 22 import static androidx.camera.core.impl.utils.AspectRatioUtil.ASPECT_RATIO_9_16; 23 import static androidx.camera.core.impl.utils.AspectRatioUtil.hasMatchingAspectRatio; 24 25 import android.graphics.ImageFormat; 26 import android.util.Pair; 27 import android.util.Rational; 28 import android.util.Size; 29 import android.view.Surface; 30 31 import androidx.camera.core.AspectRatio; 32 import androidx.camera.core.CameraSelector; 33 import androidx.camera.core.Logger; 34 import androidx.camera.core.impl.CameraInfoInternal; 35 import androidx.camera.core.impl.ImageOutputConfig; 36 import androidx.camera.core.impl.UseCaseConfig; 37 import androidx.camera.core.impl.utils.AspectRatioUtil; 38 import androidx.camera.core.impl.utils.CameraOrientationUtil; 39 import androidx.camera.core.impl.utils.CompareSizesByArea; 40 import androidx.camera.core.internal.utils.SizeUtil; 41 import androidx.camera.core.resolutionselector.AspectRatioStrategy; 42 import androidx.camera.core.resolutionselector.ResolutionFilter; 43 import androidx.camera.core.resolutionselector.ResolutionSelector; 44 import androidx.camera.core.resolutionselector.ResolutionStrategy; 45 46 import org.jspecify.annotations.NonNull; 47 import org.jspecify.annotations.Nullable; 48 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.Collections; 52 import java.util.HashMap; 53 import java.util.LinkedHashMap; 54 import java.util.List; 55 import java.util.Map; 56 57 /** 58 * The supported output sizes collector to help collect the available resolution candidate list 59 * according to the use case config and the following settings in {@link ResolutionSelector}: 60 * 61 * <ul> 62 * <li>Aspect ratio strategy 63 * <li>Resolution strategy 64 * <li>Custom resolution filter 65 * <li>High resolution enabled flags 66 * </ul> 67 */ 68 public class SupportedOutputSizesSorter { 69 private static final String TAG = "SupportedOutputSizesCollector"; 70 private final CameraInfoInternal mCameraInfoInternal; 71 private final int mSensorOrientation; 72 private final int mLensFacing; 73 private final Rational mFullFovRatio; 74 private final SupportedOutputSizesSorterLegacy mSupportedOutputSizesSorterLegacy; 75 SupportedOutputSizesSorter(@onNull CameraInfoInternal cameraInfoInternal, @Nullable Size activeArraySize)76 public SupportedOutputSizesSorter(@NonNull CameraInfoInternal cameraInfoInternal, 77 @Nullable Size activeArraySize) { 78 mCameraInfoInternal = cameraInfoInternal; 79 mSensorOrientation = mCameraInfoInternal.getSensorRotationDegrees(); 80 mLensFacing = mCameraInfoInternal.getLensFacing(); 81 mFullFovRatio = activeArraySize != null ? calculateFullFovRatioFromActiveArraySize( 82 activeArraySize) : calculateFullFovRatioFromSupportedOutputSizes( 83 mCameraInfoInternal); 84 mSupportedOutputSizesSorterLegacy = 85 new SupportedOutputSizesSorterLegacy(cameraInfoInternal, mFullFovRatio); 86 } 87 88 /** 89 * Calculates the full FOV ratio by the active array size. 90 */ calculateFullFovRatioFromActiveArraySize( @onNull Size activeArraySize)91 private @NonNull Rational calculateFullFovRatioFromActiveArraySize( 92 @NonNull Size activeArraySize) { 93 return new Rational(activeArraySize.getWidth(), activeArraySize.getHeight()); 94 } 95 96 /** 97 * Calculates the full FOV ratio by the output sizes retrieved from CameraInfoInternal. 98 * 99 * <p>For most devices, the full FOV ratio should match the aspect ratio of the max supported 100 * output sizes. The active pixel array info is not used because it may cause robolectric 101 * test to fail if it is not set in the test environment. 102 */ calculateFullFovRatioFromSupportedOutputSizes( @onNull CameraInfoInternal cameraInfoInternal)103 private @Nullable Rational calculateFullFovRatioFromSupportedOutputSizes( 104 @NonNull CameraInfoInternal cameraInfoInternal) { 105 List<Size> jpegOutputSizes = cameraInfoInternal.getSupportedResolutions(ImageFormat.JPEG); 106 if (jpegOutputSizes.isEmpty()) { 107 return null; 108 } 109 Size maxSize = Collections.max(jpegOutputSizes, new CompareSizesByArea()); 110 return new Rational(maxSize.getWidth(), maxSize.getHeight()); 111 } 112 113 /** 114 * Returns the sorted output sizes according to the use case config. 115 * 116 * <p>If ResolutionSelector is specified in the use case config, the output sizes will be 117 * sorted according to the ResolutionSelector setting and logic. Otherwise, the output sizes 118 * will be sorted according to the legacy resolution API settings and logic. 119 */ getSortedSupportedOutputSizes( @onNull UseCaseConfig<?> useCaseConfig)120 public @NonNull List<Size> getSortedSupportedOutputSizes( 121 @NonNull UseCaseConfig<?> useCaseConfig) { 122 ImageOutputConfig imageOutputConfig = (ImageOutputConfig) useCaseConfig; 123 List<Size> customOrderedResolutions = imageOutputConfig.getCustomOrderedResolutions(null); 124 125 // Directly returns the custom ordered resolutions list if it is set. 126 if (customOrderedResolutions != null) { 127 return customOrderedResolutions; 128 } 129 130 ResolutionSelector resolutionSelector = imageOutputConfig.getResolutionSelector(null); 131 List<Pair<Integer, Size[]>> customResolutions = 132 imageOutputConfig.getSupportedResolutions(null); 133 List<Size> candidateSizes = getResolutionCandidateList(customResolutions, 134 useCaseConfig.getInputFormat()); 135 136 if (resolutionSelector == null) { 137 return mSupportedOutputSizesSorterLegacy.sortSupportedOutputSizes( 138 candidateSizes, useCaseConfig); 139 } else { 140 Size maxResolution = ((ImageOutputConfig) useCaseConfig).getMaxResolution(null); 141 int targetRotation = imageOutputConfig.getTargetRotation(Surface.ROTATION_0); 142 // Applies the high resolution settings onto the resolution candidate list. 143 if (!useCaseConfig.isHighResolutionDisabled(false)) { 144 candidateSizes = applyHighResolutionSettings(candidateSizes, 145 resolutionSelector, useCaseConfig.getInputFormat()); 146 } 147 return sortSupportedOutputSizesByResolutionSelector( 148 imageOutputConfig.getResolutionSelector(), 149 candidateSizes, 150 maxResolution, 151 targetRotation, 152 mFullFovRatio, 153 mSensorOrientation, 154 mLensFacing); 155 } 156 } 157 getSizeListByFormat( @ullable List<Pair<Integer, Size[]>> resolutionsPairList, int imageFormat)158 private @Nullable List<Size> getSizeListByFormat( 159 @Nullable List<Pair<Integer, Size[]>> resolutionsPairList, 160 int imageFormat) { 161 Size[] outputSizes = null; 162 163 if (resolutionsPairList != null) { 164 for (Pair<Integer, Size[]> formatResolutionPair : resolutionsPairList) { 165 if (formatResolutionPair.first == imageFormat) { 166 outputSizes = formatResolutionPair.second; 167 break; 168 } 169 } 170 } 171 return outputSizes == null ? null : Arrays.asList(outputSizes); 172 } 173 174 /** 175 * Sorts the resolution candidate list according to the ResolutionSelector API logic. 176 * 177 * <ol> 178 * <li>Applies the aspect ratio strategy 179 * <ul> 180 * <li>Applies the aspect ratio strategy fallback rule 181 * </ul> 182 * <li>Applies the resolution strategy 183 * <ul> 184 * <li>Applies the resolution strategy fallback rule 185 * </ul> 186 * <li>Applies the resolution filter 187 * </ol> 188 * @param resolutionSelector the ResolutionSelector used to sort the candidate 189 * sizes. 190 * @param candidateSizes the candidate sizes after the high resolution processing, which 191 * will be sorted by the rule of ResolutionSelector. 192 * @param maxResolution the max resolutions which sizes larger than it will be removed 193 * from candidate sizes. 194 * @param targetRotation the target rotation to calculate the rotation degrees to the 195 * {@link ResolutionFilter}. 196 * @param fullFovRatio the full FOV's aspect ratio. 197 * @param sensorOrientation the sensor orientation of the current camera. 198 * @param lensFacing the lens facing of the current camera 199 * @return a size list which has been filtered and sorted by the specified resolution 200 * selector settings. 201 * @throws IllegalArgumentException if the specified resolution filter returns any size which 202 * is not included in the provided supported size list. 203 */ sortSupportedOutputSizesByResolutionSelector( @onNull ResolutionSelector resolutionSelector, @NonNull List<Size> candidateSizes, @Nullable Size maxResolution, int targetRotation, @NonNull Rational fullFovRatio, int sensorOrientation, int lensFacing)204 public static @NonNull List<Size> sortSupportedOutputSizesByResolutionSelector( 205 @NonNull ResolutionSelector resolutionSelector, 206 @NonNull List<Size> candidateSizes, 207 @Nullable Size maxResolution, 208 int targetRotation, 209 @NonNull Rational fullFovRatio, 210 int sensorOrientation, 211 int lensFacing) { 212 213 // Applies the aspect ratio strategy onto the resolution candidate list. 214 LinkedHashMap<Rational, List<Size>> aspectRatioSizeListMap = 215 applyAspectRatioStrategy(candidateSizes, 216 resolutionSelector.getAspectRatioStrategy(), fullFovRatio); 217 218 // Applies the max resolution setting 219 if (maxResolution != null) { 220 applyMaxResolutionRestriction(aspectRatioSizeListMap, maxResolution); 221 } 222 223 // Applies the resolution strategy onto the resolution candidate list. 224 applyResolutionStrategy(aspectRatioSizeListMap, resolutionSelector.getResolutionStrategy()); 225 226 // Collects all sizes from the sorted aspect ratio size groups into the final sorted list. 227 List<Size> resultList = new ArrayList<>(); 228 for (List<Size> sortedSizes : aspectRatioSizeListMap.values()) { 229 for (Size size : sortedSizes) { 230 // A size may exist in multiple groups in mod16 condition. Keep only one in 231 // the final list. 232 if (!resultList.contains(size)) { 233 resultList.add(size); 234 } 235 } 236 } 237 238 // Applies the resolution filter onto the resolution candidate list. 239 return applyResolutionFilter(resultList, resolutionSelector.getResolutionFilter(), 240 targetRotation, sensorOrientation, lensFacing); 241 } 242 243 /** 244 * Returns the normal supported output sizes. 245 * 246 * <p>When using camera-camera2 implementation, the output sizes are retrieved via 247 * StreamConfigurationMap#getOutputSizes(). 248 * 249 * @return the resolution candidate list sorted in descending order. 250 */ getResolutionCandidateList( @ullable List<Pair<Integer, Size[]>> customResolutions, int imageFormat)251 private @NonNull List<Size> getResolutionCandidateList( 252 @Nullable List<Pair<Integer, Size[]>> customResolutions, int imageFormat) { 253 // Tries to get the custom supported resolutions list if it is set 254 List<Size> resolutionCandidateList = getSizeListByFormat(customResolutions, imageFormat); 255 256 // Tries to get the supported output sizes from the CameraInfoInternal if both custom 257 // ordered and supported resolutions lists are not set. 258 if (resolutionCandidateList == null) { 259 resolutionCandidateList = mCameraInfoInternal.getSupportedResolutions(imageFormat); 260 } 261 262 // CameraInfoInternal.getSupportedResolutions is not guaranteed to return a modifiable list 263 // needed by Collections.sort(), so it is converted to a modifiable list here 264 resolutionCandidateList = new ArrayList<>(resolutionCandidateList); 265 266 Collections.sort(resolutionCandidateList, new CompareSizesByArea(true)); 267 268 if (resolutionCandidateList.isEmpty()) { 269 Logger.w(TAG, "The retrieved supported resolutions from camera info internal is empty" 270 + ". Format is " + imageFormat + "."); 271 } 272 273 return resolutionCandidateList; 274 } 275 276 /** 277 * Appends the high resolution supported output sizes according to the high resolution settings. 278 * 279 * <p>When using camera-camera2 implementation, the output sizes are retrieved via 280 * StreamConfigurationMap#getHighResolutionOutputSizes(). 281 * 282 * @param resolutionCandidateList the supported size list which contains only normal output 283 * sizes. 284 * @param resolutionSelector the specified resolution selector. 285 * @param imageFormat the desired image format for the target use case. 286 * @return the resolution candidate list including the high resolution output sizes sorted in 287 * descending order. 288 */ applyHighResolutionSettings( @onNull List<Size> resolutionCandidateList, @NonNull ResolutionSelector resolutionSelector, int imageFormat)289 private @NonNull List<Size> applyHighResolutionSettings( 290 @NonNull List<Size> resolutionCandidateList, 291 @NonNull ResolutionSelector resolutionSelector, int imageFormat) { 292 // Appends high resolution output sizes if high resolution is enabled by ResolutionSelector 293 if (resolutionSelector.getAllowedResolutionMode() 294 == ResolutionSelector.PREFER_HIGHER_RESOLUTION_OVER_CAPTURE_RATE) { 295 List<Size> allSizesList = new ArrayList<>(); 296 allSizesList.addAll(resolutionCandidateList); 297 allSizesList.addAll(mCameraInfoInternal.getSupportedHighResolutions(imageFormat)); 298 Collections.sort(allSizesList, new CompareSizesByArea(true)); 299 return allSizesList; 300 } 301 302 return resolutionCandidateList; 303 } 304 305 /** 306 * Applies the aspect ratio strategy onto the input resolution candidate list. 307 * 308 * @param resolutionCandidateList the supported sizes list which has been sorted in 309 * descending order. 310 * @param aspectRatioStrategy the specified aspect ratio strategy. 311 * @return an aspect ratio to size list linked hash map which the aspect ratio fallback rule 312 * is applied and is sorted against the preferred aspect ratio. 313 */ applyAspectRatioStrategy( @onNull List<Size> resolutionCandidateList, @NonNull AspectRatioStrategy aspectRatioStrategy, Rational fullFovRatio)314 private static @NonNull LinkedHashMap<Rational, List<Size>> applyAspectRatioStrategy( 315 @NonNull List<Size> resolutionCandidateList, 316 @NonNull AspectRatioStrategy aspectRatioStrategy, 317 Rational fullFovRatio) { 318 // Group output sizes by aspect ratio. 319 Map<Rational, List<Size>> aspectRatioSizeListMap = 320 groupSizesByAspectRatio(resolutionCandidateList); 321 322 // Applies the aspect ratio fallback rule 323 return applyAspectRatioStrategyFallbackRule( 324 aspectRatioSizeListMap, aspectRatioStrategy, fullFovRatio); 325 } 326 327 /** 328 * Applies the aspect ratio strategy fallback rule to the aspect ratio to size list map. 329 * 330 * @param sizeGroupsMap the aspect ratio to size list map. The size list should have been 331 * sorted in descending order. 332 * @param aspectRatioStrategy the specified aspect ratio strategy. 333 * @return an aspect ratio to size list linked hash map which the aspect ratio fallback rule 334 * is applied and is sorted against the preferred aspect ratio. 335 */ applyAspectRatioStrategyFallbackRule( @onNull Map<Rational, List<Size>> sizeGroupsMap, @NonNull AspectRatioStrategy aspectRatioStrategy, Rational fullFovRatio)336 private static LinkedHashMap<Rational, List<Size>> applyAspectRatioStrategyFallbackRule( 337 @NonNull Map<Rational, List<Size>> sizeGroupsMap, 338 @NonNull AspectRatioStrategy aspectRatioStrategy, 339 Rational fullFovRatio) { 340 // Determines the sensor resolution orientation info by the full FOV ratio. 341 boolean isSensorLandscapeResolution = fullFovRatio != null ? fullFovRatio.getNumerator() 342 >= fullFovRatio.getDenominator() : true; 343 Rational aspectRatio = getTargetAspectRatioRationalValue( 344 aspectRatioStrategy.getPreferredAspectRatio(), isSensorLandscapeResolution); 345 346 // Remove items of all other aspect ratios if the fallback rule is AspectRatioStrategy 347 // .FALLBACK_RULE_NONE 348 if (aspectRatioStrategy.getFallbackRule() == AspectRatioStrategy.FALLBACK_RULE_NONE) { 349 Rational preferredAspectRatio = getTargetAspectRatioRationalValue( 350 aspectRatioStrategy.getPreferredAspectRatio(), isSensorLandscapeResolution); 351 for (Rational ratio : new ArrayList<>(sizeGroupsMap.keySet())) { 352 if (!ratio.equals(preferredAspectRatio)) { 353 sizeGroupsMap.remove(ratio); 354 } 355 } 356 } 357 358 // Sorts the aspect ratio key set by the preferred aspect ratio. 359 List<Rational> aspectRatios = new ArrayList<>(sizeGroupsMap.keySet()); 360 Collections.sort(aspectRatios, 361 new AspectRatioUtil.CompareAspectRatiosByMappingAreaInFullFovAspectRatioSpace( 362 aspectRatio, fullFovRatio)); 363 364 // Stores the size groups into LinkedHashMap to keep the order 365 LinkedHashMap<Rational, List<Size>> sortedAspectRatioSizeListMap = new LinkedHashMap<>(); 366 for (Rational ratio : aspectRatios) { 367 sortedAspectRatioSizeListMap.put(ratio, sizeGroupsMap.get(ratio)); 368 } 369 370 return sortedAspectRatioSizeListMap; 371 } 372 373 /** 374 * Applies the resolution strategy onto the aspect ratio to size list linked hash map. 375 * 376 * <p>The resolution fallback rule is applied to filter out and sort the sizes in the 377 * underlying size list. 378 * 379 * @param sortedAspectRatioSizeListMap the aspect ratio to size list linked hash map. The 380 * entries order should not be changed. 381 * @param resolutionStrategy the resolution strategy to sort the candidate 382 * resolutions. 383 */ applyResolutionStrategy( @onNull LinkedHashMap<Rational, List<Size>> sortedAspectRatioSizeListMap, @Nullable ResolutionStrategy resolutionStrategy)384 private static void applyResolutionStrategy( 385 @NonNull LinkedHashMap<Rational, List<Size>> sortedAspectRatioSizeListMap, 386 @Nullable ResolutionStrategy resolutionStrategy) { 387 if (resolutionStrategy == null) { 388 return; 389 } 390 391 // Applies the resolution strategy with the specified fallback rule 392 for (Rational key : sortedAspectRatioSizeListMap.keySet()) { 393 applyResolutionStrategyFallbackRule(sortedAspectRatioSizeListMap.get(key), 394 resolutionStrategy); 395 } 396 } 397 398 /** 399 * Applies the resolution strategy fallback rule to the size list. 400 * 401 * @param supportedSizesList the supported sizes list which has been sorted in descending order. 402 * @param resolutionStrategy the resolution strategy to sort the candidate resolutions. 403 */ applyResolutionStrategyFallbackRule( @onNull List<Size> supportedSizesList, @NonNull ResolutionStrategy resolutionStrategy)404 private static void applyResolutionStrategyFallbackRule( 405 @NonNull List<Size> supportedSizesList, 406 @NonNull ResolutionStrategy resolutionStrategy) { 407 if (supportedSizesList.isEmpty()) { 408 return; 409 } 410 Integer fallbackRule = resolutionStrategy.getFallbackRule(); 411 412 if (resolutionStrategy.equals(ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY)) { 413 // Do nothing for HIGHEST_AVAILABLE_STRATEGY case. 414 return; 415 } 416 417 Size boundSize = resolutionStrategy.getBoundSize(); 418 419 switch (fallbackRule) { 420 case ResolutionStrategy.FALLBACK_RULE_NONE: 421 sortSupportedSizesByFallbackRuleNone(supportedSizesList, boundSize); 422 break; 423 case ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER: 424 sortSupportedSizesByFallbackRuleClosestHigherThenLower(supportedSizesList, 425 boundSize, true); 426 break; 427 case ResolutionStrategy.FALLBACK_RULE_CLOSEST_HIGHER: 428 sortSupportedSizesByFallbackRuleClosestHigherThenLower(supportedSizesList, 429 boundSize, false); 430 break; 431 case ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER: 432 sortSupportedSizesByFallbackRuleClosestLowerThenHigher(supportedSizesList, 433 boundSize, true); 434 break; 435 case ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER: 436 sortSupportedSizesByFallbackRuleClosestLowerThenHigher(supportedSizesList, 437 boundSize, false); 438 break; 439 default: 440 break; 441 } 442 } 443 444 /** 445 * Applies the max resolution restriction. 446 * 447 * <p>Filters out the output sizes that exceed the max resolution in area size. 448 * 449 * @param sortedAspectRatioSizeListMap the aspect ratio to size list linked hash map. The 450 * entries order should not be changed. 451 * @param maxResolution the max resolution size. 452 */ applyMaxResolutionRestriction( @onNull LinkedHashMap<Rational, List<Size>> sortedAspectRatioSizeListMap, @NonNull Size maxResolution)453 private static void applyMaxResolutionRestriction( 454 @NonNull LinkedHashMap<Rational, List<Size>> sortedAspectRatioSizeListMap, 455 @NonNull Size maxResolution) { 456 int maxResolutionAreaSize = SizeUtil.getArea(maxResolution); 457 for (Rational key : sortedAspectRatioSizeListMap.keySet()) { 458 List<Size> supportedSizesList = sortedAspectRatioSizeListMap.get(key); 459 List<Size> filteredResultList = new ArrayList<>(); 460 for (Size size : supportedSizesList) { 461 if (SizeUtil.getArea(size) <= maxResolutionAreaSize) { 462 filteredResultList.add(size); 463 } 464 } 465 supportedSizesList.clear(); 466 supportedSizesList.addAll(filteredResultList); 467 } 468 } 469 470 /** 471 * Applies the resolution filtered to the sorted output size list. 472 * 473 * @param sizeList the supported size list which has been filtered and sorted by the 474 * specified aspect ratio, resolution strategies. 475 * @param resolutionFilter the specified resolution filter. 476 * @param targetRotation the use case target rotation info 477 * @return the result size list applied the specified resolution filter. 478 * @throws IllegalArgumentException if the specified resolution filter returns any size which 479 * is not included in the provided supported size list. 480 */ applyResolutionFilter(@onNull List<Size> sizeList, @Nullable ResolutionFilter resolutionFilter, @ImageOutputConfig.RotationValue int targetRotation, int sensorOrientation, int lensFacing)481 private static @NonNull List<Size> applyResolutionFilter(@NonNull List<Size> sizeList, 482 @Nullable ResolutionFilter resolutionFilter, 483 @ImageOutputConfig.RotationValue int targetRotation, 484 int sensorOrientation, 485 int lensFacing) { 486 if (resolutionFilter == null) { 487 return sizeList; 488 } 489 490 // Invokes ResolutionFilter#filter() to filter/sort and return the result if it is 491 // specified. 492 int destRotationDegrees = CameraOrientationUtil.surfaceRotationToDegrees( 493 targetRotation); 494 int rotationDegrees = 495 CameraOrientationUtil.getRelativeImageRotation(destRotationDegrees, 496 sensorOrientation, 497 lensFacing == CameraSelector.LENS_FACING_BACK); 498 List<Size> filteredResultList = resolutionFilter.filter(new ArrayList<>(sizeList), 499 rotationDegrees); 500 if (sizeList.containsAll(filteredResultList)) { 501 return filteredResultList; 502 } else { 503 throw new IllegalArgumentException("The returned sizes list of the resolution " 504 + "filter must be a subset of the provided sizes list."); 505 } 506 } 507 508 /** 509 * Sorts the size list for {@link ResolutionStrategy#FALLBACK_RULE_NONE}. 510 * 511 * @param supportedSizesList the supported sizes list which has been sorted in descending order. 512 * @param boundSize the resolution strategy bound size. 513 */ sortSupportedSizesByFallbackRuleNone( @onNull List<Size> supportedSizesList, @NonNull Size boundSize)514 private static void sortSupportedSizesByFallbackRuleNone( 515 @NonNull List<Size> supportedSizesList, @NonNull Size boundSize) { 516 boolean containsBoundSize = supportedSizesList.contains(boundSize); 517 supportedSizesList.clear(); 518 if (containsBoundSize) { 519 supportedSizesList.add(boundSize); 520 } 521 } 522 523 /** 524 * Sorts the size list for {@link ResolutionStrategy#FALLBACK_RULE_CLOSEST_HIGHER_THEN_LOWER} 525 * or {@link ResolutionStrategy#FALLBACK_RULE_CLOSEST_HIGHER}. 526 * 527 * @param supportedSizesList the supported sizes list which has been sorted in descending order. 528 * @param boundSize the resolution strategy bound size. 529 * @param keepLowerSizes keeps the sizes lower than the bound size in the result list if 530 * this is {@code true}. 531 */ sortSupportedSizesByFallbackRuleClosestHigherThenLower( @onNull List<Size> supportedSizesList, @NonNull Size boundSize, boolean keepLowerSizes)532 static void sortSupportedSizesByFallbackRuleClosestHigherThenLower( 533 @NonNull List<Size> supportedSizesList, @NonNull Size boundSize, 534 boolean keepLowerSizes) { 535 List<Size> lowerSizes = new ArrayList<>(); 536 537 for (int i = supportedSizesList.size() - 1; i >= 0; i--) { 538 Size outputSize = supportedSizesList.get(i); 539 if (outputSize.getWidth() < boundSize.getWidth() 540 || outputSize.getHeight() < boundSize.getHeight()) { 541 // The supportedSizesList is in descending order. Checking and put the 542 // bounding-below size at position 0 so that the largest smaller resolution 543 // will be put in the first position finally. 544 lowerSizes.add(0, outputSize); 545 } else { 546 break; 547 } 548 } 549 // Removes the lower sizes from the list 550 supportedSizesList.removeAll(lowerSizes); 551 // Reverses the list so that the smallest larger resolution will be put in the first 552 // position. 553 Collections.reverse(supportedSizesList); 554 if (keepLowerSizes) { 555 // Appends the lower sizes to the tail 556 supportedSizesList.addAll(lowerSizes); 557 } 558 } 559 560 /** 561 * Sorts the size list for {@link ResolutionStrategy#FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER} 562 * or {@link ResolutionStrategy#FALLBACK_RULE_CLOSEST_LOWER}. 563 * 564 * @param supportedSizesList the supported sizes list which has been sorted in descending order. 565 * @param boundSize the resolution strategy bound size. 566 * @param keepHigherSizes keeps the sizes higher than the bound size in the result list if 567 * this is {@code true}. 568 */ sortSupportedSizesByFallbackRuleClosestLowerThenHigher( @onNull List<Size> supportedSizesList, @NonNull Size boundSize, boolean keepHigherSizes)569 private static void sortSupportedSizesByFallbackRuleClosestLowerThenHigher( 570 @NonNull List<Size> supportedSizesList, @NonNull Size boundSize, 571 boolean keepHigherSizes) { 572 List<Size> higherSizes = new ArrayList<>(); 573 574 for (int i = 0; i < supportedSizesList.size(); i++) { 575 Size outputSize = supportedSizesList.get(i); 576 if (outputSize.getWidth() > boundSize.getWidth() 577 || outputSize.getHeight() > boundSize.getHeight()) { 578 // The supportedSizesList is in descending order. Checking and put the 579 // bounding-above size at position 0 so that the smallest larger resolution 580 // will be put in the first position finally. 581 higherSizes.add(0, outputSize); 582 } else { 583 // Breaks the for-loop to keep the equal-to or lower sizes in the list. 584 break; 585 } 586 } 587 // Removes the higher sizes from the list 588 supportedSizesList.removeAll(higherSizes); 589 if (keepHigherSizes) { 590 // Appends the higher sizes to the tail 591 supportedSizesList.addAll(higherSizes); 592 } 593 } 594 595 /** 596 * Returns the target aspect ratio rational value according to the ResolutionSelector settings. 597 */ getTargetAspectRatioRationalValue(@spectRatio.Ratio int aspectRatio, boolean isSensorLandscapeResolution)598 static @Nullable Rational getTargetAspectRatioRationalValue(@AspectRatio.Ratio int aspectRatio, 599 boolean isSensorLandscapeResolution) { 600 Rational outputRatio = null; 601 602 switch (aspectRatio) { 603 case AspectRatio.RATIO_4_3: 604 outputRatio = isSensorLandscapeResolution ? ASPECT_RATIO_4_3 605 : ASPECT_RATIO_3_4; 606 break; 607 case AspectRatio.RATIO_16_9: 608 outputRatio = isSensorLandscapeResolution ? ASPECT_RATIO_16_9 609 : ASPECT_RATIO_9_16; 610 break; 611 case AspectRatio.RATIO_DEFAULT: 612 break; 613 default: 614 Logger.e(TAG, "Undefined target aspect ratio: " + aspectRatio); 615 } 616 617 return outputRatio; 618 } 619 620 /** 621 * Returns the grouping aspect ratio keys of the input resolution list. 622 * 623 * <p>Some sizes might be mod16 case. When grouping, those sizes will be grouped into an 624 * existing aspect ratio group if the aspect ratio can match by the mod16 rule. 625 */ getResolutionListGroupingAspectRatioKeys( @onNull List<Size> resolutionCandidateList)626 static @NonNull List<Rational> getResolutionListGroupingAspectRatioKeys( 627 @NonNull List<Size> resolutionCandidateList) { 628 List<Rational> aspectRatios = new ArrayList<>(); 629 630 // Adds the default 4:3 and 16:9 items first to avoid their mod16 sizes to create 631 // additional items. 632 aspectRatios.add(ASPECT_RATIO_4_3); 633 aspectRatios.add(ASPECT_RATIO_16_9); 634 635 // Tries to find the aspect ratio which the target size belongs to. 636 for (Size size : resolutionCandidateList) { 637 Rational newRatio = new Rational(size.getWidth(), size.getHeight()); 638 boolean aspectRatioFound = aspectRatios.contains(newRatio); 639 640 // The checking size might be a mod16 size which can be mapped to an existing aspect 641 // ratio group. 642 if (!aspectRatioFound) { 643 boolean hasMatchingAspectRatio = false; 644 for (Rational aspectRatio : aspectRatios) { 645 if (hasMatchingAspectRatio(size, aspectRatio)) { 646 hasMatchingAspectRatio = true; 647 break; 648 } 649 } 650 if (!hasMatchingAspectRatio) { 651 aspectRatios.add(newRatio); 652 } 653 } 654 } 655 656 return aspectRatios; 657 } 658 659 /** 660 * Groups the input sizes into an aspect ratio to size list map. 661 */ groupSizesByAspectRatio(@onNull List<Size> sizes)662 static Map<Rational, List<Size>> groupSizesByAspectRatio(@NonNull List<Size> sizes) { 663 Map<Rational, List<Size>> aspectRatioSizeListMap = new HashMap<>(); 664 665 List<Rational> aspectRatioKeys = getResolutionListGroupingAspectRatioKeys(sizes); 666 667 for (Rational aspectRatio : aspectRatioKeys) { 668 aspectRatioSizeListMap.put(aspectRatio, new ArrayList<>()); 669 } 670 671 for (Size outputSize : sizes) { 672 for (Rational key : aspectRatioSizeListMap.keySet()) { 673 // Put the size into all groups that is matched in mod16 condition since a size 674 // may match multiple aspect ratio in mod16 algorithm. 675 if (hasMatchingAspectRatio(outputSize, key)) { 676 aspectRatioSizeListMap.get(key).add(outputSize); 677 } 678 } 679 } 680 681 return aspectRatioSizeListMap; 682 } 683 } 684