SettingsUtil.java revision 6607dae6c4d1ee5bbf572695c2042d4d9129a730
1/* 2 * Copyright (C) 2014 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 17package com.android.camera.settings; 18 19import android.app.AlertDialog; 20import android.content.DialogInterface; 21import android.hardware.Camera; 22import android.hardware.Camera.Parameters; 23import android.media.CamcorderProfile; 24import android.util.SparseArray; 25 26import com.android.camera.cameradevice.CameraManager; 27import com.android.camera.cameradevice.CameraSettings; 28import com.android.camera.debug.Log; 29import com.android.camera.settings.SettingsManager.SettingsCapabilities; 30import com.android.camera.util.Callback; 31import com.android.camera.cameradevice.Size; 32import com.android.camera2.R; 33 34import java.util.ArrayList; 35import java.util.Collections; 36import java.util.Comparator; 37import java.util.LinkedList; 38import java.util.List; 39 40/** 41 * Utility functions around camera settings. 42 */ 43public class SettingsUtil { 44 /** The selected Camera sizes. */ 45 public static class SelectedPictureSizes { 46 public Size large; 47 public Size medium; 48 public Size small; 49 50 /** 51 * This takes a string preference describing the desired resolution and 52 * returns the camera size it represents. <br/> 53 * It supports historical values of SIZE_LARGE, SIZE_MEDIUM, and 54 * SIZE_SMALL as well as resolutions separated by an x i.e. "1024x576" <br/> 55 * If it fails to parse the string, it will return the old SIZE_LARGE 56 * value. 57 * 58 * @param sizeSetting the preference string to convert to a size 59 * @param supportedSizes all possible camera sizes that are supported 60 * @return the size that this setting represents 61 */ 62 public Size getFromSetting(String sizeSetting, List<Size> supportedSizes) { 63 if (SIZE_LARGE.equals(sizeSetting)) { 64 return large; 65 } else if (SIZE_MEDIUM.equals(sizeSetting)) { 66 return medium; 67 } else if (SIZE_SMALL.equals(sizeSetting)) { 68 return small; 69 } else if (sizeSetting != null && sizeSetting.split("x").length == 2) { 70 Size desiredSize = sizeFromString(sizeSetting); 71 if (supportedSizes.contains(desiredSize)) { 72 return desiredSize; 73 } 74 } 75 return large; 76 } 77 78 @Override 79 public String toString() { 80 return "SelectedPictureSizes: " + large + ", " + medium + ", " + small; 81 } 82 } 83 84 /** The selected {@link CamcorderProfile} qualities. */ 85 public static class SelectedVideoQualities { 86 public int large = -1; 87 public int medium = -1; 88 public int small = -1; 89 90 public int getFromSetting(String sizeSetting) { 91 // Sanitize the value to be either small, medium or large. Default 92 // to the latter. 93 if (!SIZE_SMALL.equals(sizeSetting) && !SIZE_MEDIUM.equals(sizeSetting)) { 94 sizeSetting = SIZE_LARGE; 95 } 96 97 if (SIZE_LARGE.equals(sizeSetting)) { 98 return large; 99 } else if (SIZE_MEDIUM.equals(sizeSetting)) { 100 return medium; 101 } else { 102 return small; 103 } 104 } 105 } 106 107 private static final Log.Tag TAG = new Log.Tag("SettingsUtil"); 108 109 /** Enable debug output. */ 110 private static final boolean DEBUG = false; 111 112 private static final String SIZE_LARGE = "large"; 113 private static final String SIZE_MEDIUM = "medium"; 114 private static final String SIZE_SMALL = "small"; 115 116 /** The ideal "medium" picture size is 50% of "large". */ 117 private static final float MEDIUM_RELATIVE_PICTURE_SIZE = 0.5f; 118 119 /** The ideal "small" picture size is 25% of "large". */ 120 private static final float SMALL_RELATIVE_PICTURE_SIZE = 0.25f; 121 122 /** Video qualities sorted by size. */ 123 public static int[] sVideoQualities = new int[] { 124 CamcorderProfile.QUALITY_1080P, 125 CamcorderProfile.QUALITY_720P, 126 CamcorderProfile.QUALITY_480P, 127 CamcorderProfile.QUALITY_CIF, 128 CamcorderProfile.QUALITY_QVGA, 129 CamcorderProfile.QUALITY_QCIF 130 }; 131 132 public static SparseArray<SelectedPictureSizes> sCachedSelectedPictureSizes = 133 new SparseArray<SelectedPictureSizes>(2); 134 public static SparseArray<SelectedVideoQualities> sCachedSelectedVideoQualities = 135 new SparseArray<SelectedVideoQualities>(2); 136 137 /** 138 * Based on the selected size, this method selects the matching concrete 139 * resolution and sets it as the picture size. 140 * 141 * @param sizeSetting The setting selected by the user. One of "large", 142 * "medium, "small" or two integers separated by "x". 143 * @param supported The list of supported resolutions. 144 * @param settings The Camera settings to set the selected picture 145 * resolution on. 146 * @param cameraId This is used for caching the results for finding the 147 * different sizes. 148 */ 149 public static void setCameraPictureSize(String sizeSetting, List<Size> supported, 150 CameraSettings settings, int cameraId) { 151 Size selectedSize = getCameraPictureSize(sizeSetting, supported, cameraId); 152 Log.d(TAG, "Selected " + sizeSetting + " resolution: " + selectedSize.width() + "x" + 153 selectedSize.height()); 154 settings.setPhotoSize(selectedSize); 155 } 156 157 /** 158 * Based on the selected size, this method returns the matching concrete 159 * resolution. 160 * 161 * @param sizeSetting The setting selected by the user. One of "large", 162 * "medium, "small". 163 * @param supported The list of supported resolutions. 164 * @param cameraId This is used for caching the results for finding the 165 * different sizes. 166 */ 167 public static Size getPhotoSize(String sizeSetting, List<Size> supported, int cameraId) { 168 if (ResolutionUtil.NEXUS_5_LARGE_16_BY_9.equals(sizeSetting)) { 169 return ResolutionUtil.NEXUS_5_LARGE_16_BY_9_SIZE; 170 } 171 Size selectedSize = getCameraPictureSize(sizeSetting, supported, cameraId); 172 return selectedSize; 173 } 174 175 /** 176 * Based on the selected size (large, medium or small), and the list of 177 * supported resolutions, this method selects and returns the best matching 178 * picture size. 179 * 180 * @param sizeSetting The setting selected by the user. One of "large", 181 * "medium, "small". 182 * @param supported The list of supported resolutions. 183 * @param cameraId This is used for caching the results for finding the 184 * different sizes. 185 * @return The selected size. 186 */ 187 private static Size getCameraPictureSize(String sizeSetting, List<Size> supported, 188 int cameraId) { 189 return getSelectedCameraPictureSizes(supported, cameraId).getFromSetting(sizeSetting, 190 supported); 191 } 192 193 /** 194 * Based on the list of supported resolutions, this method selects the ones 195 * that shall be selected for being 'large', 'medium' and 'small'. 196 * 197 * @return It's guaranteed that all three sizes are filled. If less than 198 * three sizes are supported, the selected sizes might contain 199 * duplicates. 200 */ 201 static SelectedPictureSizes getSelectedCameraPictureSizes(List<Size> supported, int cameraId) { 202 List<Size> supportedCopy = new LinkedList<Size>(supported); 203 if (sCachedSelectedPictureSizes.get(cameraId) != null) { 204 return sCachedSelectedPictureSizes.get(cameraId); 205 } 206 if (supportedCopy == null) { 207 return null; 208 } 209 210 SelectedPictureSizes selectedSizes = new SelectedPictureSizes(); 211 212 // Sort supported sizes by total pixel count, descending. 213 Collections.sort(supportedCopy, new Comparator<Size>() { 214 @Override 215 public int compare(Size lhs, Size rhs) { 216 int leftArea = lhs.width() * lhs.height(); 217 int rightArea = rhs.width() * rhs.height(); 218 return rightArea - leftArea; 219 } 220 }); 221 if (DEBUG) { 222 Log.d(TAG, "Supported Sizes:"); 223 for (Size size : supportedCopy) { 224 Log.d(TAG, " --> " + size.width() + "x" + size.height() + " " 225 + ((size.width() * size.height()) / 1000000f) + " - " 226 + (size.width() / (float) size.height())); 227 } 228 } 229 230 // Large size is always the size with the most pixels reported. 231 selectedSizes.large = supportedCopy.remove(0); 232 233 // If possible we want to find medium and small sizes with the same 234 // aspect ratio as 'large'. 235 final float targetAspectRatio = selectedSizes.large.width() 236 / (float) selectedSizes.large.height(); 237 238 // Create a list of sizes with the same aspect ratio as "large" which we 239 // will search in primarily. 240 ArrayList<Size> aspectRatioMatches = new ArrayList<Size>(); 241 for (Size size : supportedCopy) { 242 float aspectRatio = size.width() / (float) size.height(); 243 // Allow for small rounding errors in aspect ratio. 244 if (Math.abs(aspectRatio - targetAspectRatio) < 0.01) { 245 aspectRatioMatches.add(size); 246 } 247 } 248 249 // If we have at least two more resolutions that match the 'large' 250 // aspect ratio, use that list to find small and medium sizes. If not, 251 // use the full list with any aspect ratio. 252 final List<Size> searchList = (aspectRatioMatches.size() >= 2) ? aspectRatioMatches 253 : supportedCopy; 254 255 // Edge cases: If there are no further supported resolutions, use the 256 // only one we have. 257 // If there is only one remaining, use it for small and medium. If there 258 // are two, use the two for small and medium. 259 // These edge cases should never happen on a real device, but might 260 // happen on test devices and emulators. 261 if (searchList.isEmpty()) { 262 Log.w(TAG, "Only one supported resolution."); 263 selectedSizes.medium = selectedSizes.large; 264 selectedSizes.small = selectedSizes.large; 265 } else if (searchList.size() == 1) { 266 Log.w(TAG, "Only two supported resolutions."); 267 selectedSizes.medium = searchList.get(0); 268 selectedSizes.small = searchList.get(0); 269 } else if (searchList.size() == 2) { 270 Log.w(TAG, "Exactly three supported resolutions."); 271 selectedSizes.medium = searchList.get(0); 272 selectedSizes.small = searchList.get(1); 273 } else { 274 275 // Based on the large pixel count, determine the target pixel count 276 // for medium and small. 277 final int largePixelCount = selectedSizes.large.width() * selectedSizes.large.height(); 278 final int mediumTargetPixelCount = (int) (largePixelCount * MEDIUM_RELATIVE_PICTURE_SIZE); 279 final int smallTargetPixelCount = (int) (largePixelCount * SMALL_RELATIVE_PICTURE_SIZE); 280 281 int mediumSizeIndex = findClosestSize(searchList, mediumTargetPixelCount); 282 int smallSizeIndex = findClosestSize(searchList, smallTargetPixelCount); 283 284 // If the selected sizes are the same, move the small size one down 285 // or 286 // the medium size one up. 287 if (searchList.get(mediumSizeIndex).equals(searchList.get(smallSizeIndex))) { 288 if (smallSizeIndex < (searchList.size() - 1)) { 289 smallSizeIndex += 1; 290 } else { 291 mediumSizeIndex -= 1; 292 } 293 } 294 selectedSizes.medium = searchList.get(mediumSizeIndex); 295 selectedSizes.small = searchList.get(smallSizeIndex); 296 } 297 sCachedSelectedPictureSizes.put(cameraId, selectedSizes); 298 return selectedSizes; 299 } 300 301 /** 302 * Determines the video quality for large/medium/small for the given camera. 303 * Returns the one matching the given setting. Defaults to 'large' of the 304 * qualitySetting does not match either large. medium or small. 305 * 306 * @param qualitySetting One of 'large', 'medium', 'small'. 307 * @param cameraId The ID of the camera for which to get the quality 308 * setting. 309 * @return The CamcorderProfile quality setting. 310 */ 311 public static int getVideoQuality(String qualitySetting, int cameraId) { 312 return getSelectedVideoQualities(cameraId).getFromSetting(qualitySetting); 313 } 314 315 static SelectedVideoQualities getSelectedVideoQualities(int cameraId) { 316 if (sCachedSelectedVideoQualities.get(cameraId) != null) { 317 return sCachedSelectedVideoQualities.get(cameraId); 318 } 319 320 // Go through the sizes in descending order, see if they are supported, 321 // and set large/medium/small accordingly. 322 // If no quality is supported at all, the first call to 323 // getNextSupportedQuality will throw an exception. 324 // If only one quality is supported, then all three selected qualities 325 // will be the same. 326 int largeIndex = getNextSupportedVideoQualityIndex(cameraId, -1); 327 int mediumIndex = getNextSupportedVideoQualityIndex(cameraId, largeIndex); 328 int smallIndex = getNextSupportedVideoQualityIndex(cameraId, mediumIndex); 329 330 SelectedVideoQualities selectedQualities = new SelectedVideoQualities(); 331 selectedQualities.large = sVideoQualities[largeIndex]; 332 selectedQualities.medium = sVideoQualities[mediumIndex]; 333 selectedQualities.small = sVideoQualities[smallIndex]; 334 sCachedSelectedVideoQualities.put(cameraId, selectedQualities); 335 return selectedQualities; 336 } 337 338 /** 339 * Starting from 'start' this method returns the next supported video 340 * quality. 341 */ 342 private static int getNextSupportedVideoQualityIndex(int cameraId, int start) { 343 for (int i = start + 1; i < sVideoQualities.length; ++i) { 344 if (CamcorderProfile.hasProfile(cameraId, sVideoQualities[i])) { 345 // We found a new supported quality. 346 return i; 347 } 348 } 349 350 // Failed to find another supported quality. 351 if (start < 0 || start >= sVideoQualities.length) { 352 // This means we couldn't find any supported quality. 353 throw new IllegalArgumentException("Could not find supported video qualities."); 354 } 355 356 // We previously found a larger supported size. In this edge case, just 357 // return the same index as the previous size. 358 return start; 359 } 360 361 /** 362 * Returns the index of the size within the given list that is closest to 363 * the given target pixel count. 364 */ 365 private static int findClosestSize(List<Size> sortedSizes, int targetPixelCount) { 366 int closestMatchIndex = 0; 367 int closestMatchPixelCountDiff = Integer.MAX_VALUE; 368 369 for (int i = 0; i < sortedSizes.size(); ++i) { 370 Size size = sortedSizes.get(i); 371 int pixelCountDiff = Math.abs((size.width() * size.height()) - targetPixelCount); 372 if (pixelCountDiff < closestMatchPixelCountDiff) { 373 closestMatchIndex = i; 374 closestMatchPixelCountDiff = pixelCountDiff; 375 } 376 } 377 return closestMatchIndex; 378 } 379 380 /** 381 * This is used to serialize a size to a string for storage in settings 382 * 383 * @param size The size to serialize. 384 * @return the string to be saved in preferences 385 */ 386 public static String sizeToSetting(Size size) { 387 return ((Integer) size.width()).toString() + "x" + ((Integer) size.height()).toString(); 388 } 389 390 /** 391 * This parses a setting string and returns the representative size. 392 * 393 * @param sizeSetting The string to parse. 394 * @return the represented Size. 395 */ 396 static public Size sizeFromString(String sizeSetting) { 397 String[] parts = sizeSetting.split("x"); 398 if (parts.length == 2) { 399 return new Size(Integer.valueOf(parts[0]), Integer.valueOf(parts[1])); 400 } else { 401 return null; 402 } 403 } 404 405 /** 406 * Determines and returns the capabilities of the given camera. 407 */ 408 public static SettingsCapabilities 409 getSettingsCapabilities(CameraManager.CameraProxy camera) { 410 final Parameters parameters = camera.getParameters(); 411 return (new SettingsCapabilities() { 412 @Override 413 public String[] getSupportedExposureValues() { 414 int max = parameters.getMaxExposureCompensation(); 415 int min = parameters.getMinExposureCompensation(); 416 float step = parameters.getExposureCompensationStep(); 417 int maxValue = Math.min(3, (int) Math.floor(max * step)); 418 int minValue = Math.max(-3, (int) Math.ceil(min * step)); 419 String[] entryValues = new String[maxValue - minValue + 1]; 420 for (int i = minValue; i <= maxValue; ++i) { 421 entryValues[i - minValue] = Integer.toString(Math.round(i / step)); 422 } 423 return entryValues; 424 } 425 426 @Override 427 public String[] getSupportedCameraIds() { 428 int numberOfCameras = Camera.getNumberOfCameras(); 429 String[] cameraIds = new String[numberOfCameras]; 430 for (int i = 0; i < numberOfCameras; i++) { 431 cameraIds[i] = "" + i; 432 } 433 return cameraIds; 434 } 435 }); 436 } 437 438 /** 439 * Updates an AlertDialog.Builder to explain what it means to enable 440 * location on captures. 441 */ 442 public static AlertDialog.Builder getFirstTimeLocationAlertBuilder( 443 AlertDialog.Builder builder, Callback<Boolean> callback) { 444 if (callback == null) { 445 return null; 446 } 447 448 getLocationAlertBuilder(builder, callback) 449 .setMessage(R.string.remember_location_prompt); 450 451 return builder; 452 } 453 454 /** 455 * Updates an AlertDialog.Builder for choosing whether to include location 456 * on captures. 457 */ 458 public static AlertDialog.Builder getLocationAlertBuilder(AlertDialog.Builder builder, 459 final Callback<Boolean> callback) { 460 if (callback == null) { 461 return null; 462 } 463 464 builder.setTitle(R.string.remember_location_title) 465 .setPositiveButton(R.string.remember_location_yes, 466 new DialogInterface.OnClickListener() { 467 @Override 468 public void onClick(DialogInterface dialog, int arg1) { 469 callback.onCallback(true); 470 } 471 }) 472 .setNegativeButton(R.string.remember_location_no, 473 new DialogInterface.OnClickListener() { 474 @Override 475 public void onClick(DialogInterface dialog, int arg1) { 476 callback.onCallback(false); 477 } 478 }); 479 480 return builder; 481 } 482 483 /** 484 * Gets the first camera facing the given direction. 485 * 486 * @param facing Either {@link android.hardware.Camera.CameraInfo#CAMERA_FACING_BACK} or 487 * {@link android.hardware.Camera.CameraInfo#CAMERA_FACING_FRONT}. 488 * @return The ID of the first camera matching the given direction, or 489 * -1, if no camera with the given facing was found. 490 */ 491 public static int getCameraId(int facing) { 492 int numCameras = Camera.getNumberOfCameras(); 493 for (int i = 0; i < numCameras; ++i) { 494 Camera.CameraInfo info = new Camera.CameraInfo(); 495 Camera.getCameraInfo(i, info); 496 if (info.facing == facing) { 497 return i; 498 } 499 } 500 return -1; 501 } 502} 503