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