blob: c913190f808a1f7b927e6822022beb0239406d84 [file] [log] [blame]
Sascha Haeberling3b0ab892014-01-29 20:54:39 +01001/*
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
Sascha Haeberlingde303232014-02-07 02:30:53 +010019import android.app.AlertDialog;
20import android.content.DialogInterface;
21import android.hardware.Camera;
Sascha Haeberling3b0ab892014-01-29 20:54:39 +010022import android.hardware.Camera.Parameters;
23import android.hardware.Camera.Size;
Sascha Haeberlingde303232014-02-07 02:30:53 +010024import android.media.CamcorderProfile;
Sascha Haeberling6ccec202014-03-11 09:44:34 -070025import android.util.SparseArray;
Sascha Haeberling3b0ab892014-01-29 20:54:39 +010026
Sascha Haeberlingde303232014-02-07 02:30:53 +010027import com.android.camera.app.CameraManager;
Angus Kong5596b4c2014-03-11 16:27:30 -070028import com.android.camera.debug.Log;
Sascha Haeberlingde303232014-02-07 02:30:53 +010029import com.android.camera.settings.SettingsManager.SettingsCapabilities;
30import com.android.camera.util.Callback;
31import com.android.camera2.R;
32
Sascha Haeberling3b0ab892014-01-29 20:54:39 +010033import 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 {
Sascha Haeberling6ccec202014-03-11 09:44:34 -070042 /** 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
Angus Kong5596b4c2014-03-11 16:27:30 -070098 private static final Log.Tag TAG = new Log.Tag("SettingsUtil");
Sascha Haeberling3b0ab892014-01-29 20:54:39 +010099
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
Sascha Haeberlingde303232014-02-07 02:30:53 +0100113 /** 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
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700123 public static SparseArray<SelectedPictureSizes> sCachedSelectedPictureSizes =
124 new SparseArray<SelectedPictureSizes>(2);
125 public static SparseArray<SelectedVideoQualities> sCachedSelectedVideoQualities =
126 new SparseArray<SelectedVideoQualities>(2);
127
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100128 /**
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.
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700137 * @param cameraId This is used for caching the results for finding the
138 * different sizes.
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100139 */
140 public static void setCameraPictureSize(String sizeSetting, List<Size> supported,
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700141 Parameters parameters, int cameraId) {
142 Size selectedSize = getCameraPictureSize(sizeSetting, supported, cameraId);
Angus Kong5596b4c2014-03-11 16:27:30 -0700143 Log.d(TAG, "Selected " + sizeSetting + " resolution: " + selectedSize.width + "x" +
144 selectedSize.height);
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100145 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.
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700156 * @param cameraId This is used for caching the results for finding the
157 * different sizes.
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100158 * @return The selected size.
159 */
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700160 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);
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100176 }
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700177 if (supported == null) {
178 return null;
179 }
180
181 SelectedPictureSizes selectedSizes = new SelectedPictureSizes();
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100182
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.
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700202 selectedSizes.large = supported.remove(0);
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100203
204 // If possible we want to find medium and small sizes with the same
205 // aspect ratio as 'large'.
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700206 final float targetAspectRatio = selectedSizes.large.width
207 / (float) selectedSizes.large.height;
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100208
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.");
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700234 selectedSizes.medium = selectedSizes.large;
235 selectedSizes.small = selectedSizes.large;
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100236 } else if (searchList.size() == 1) {
237 Log.w(TAG, "Only two supported resolutions.");
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700238 selectedSizes.medium = searchList.get(0);
239 selectedSizes.small = searchList.get(0);
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100240 } else if (searchList.size() == 2) {
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700241 Log.w(TAG, "Exactly three supported resolutions.");
242 selectedSizes.medium = searchList.get(0);
243 selectedSizes.small = searchList.get(1);
244 } else {
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100245
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700246 // 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);
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100251
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700252 int mediumSizeIndex = findClosestSize(searchList, mediumTargetPixelCount);
253 int smallSizeIndex = findClosestSize(searchList, smallTargetPixelCount);
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100254
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700255 // 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 }
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100264 }
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700265 selectedSizes.medium = searchList.get(mediumSizeIndex);
266 selectedSizes.small = searchList.get(smallSizeIndex);
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100267 }
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700268 sCachedSelectedPictureSizes.put(cameraId, selectedSizes);
269 return selectedSizes;
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100270 }
271
272 /**
Sascha Haeberlingde303232014-02-07 02:30:53 +0100273 * 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) {
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700283 return getSelectedVideoQualities(cameraId).getFromSetting(qualitySetting);
284 }
285
286 static SelectedVideoQualities getSelectedVideoQualities(int cameraId) {
287 if (sCachedSelectedVideoQualities.get(cameraId) != null) {
288 return sCachedSelectedVideoQualities.get(cameraId);
Sascha Haeberlingde303232014-02-07 02:30:53 +0100289 }
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.
Paul Chang8d85af52014-03-24 12:05:45 -0400297 int largeIndex = getNextSupportedVideoQualityIndex(cameraId, -1);
298 int mediumIndex = getNextSupportedVideoQualityIndex(cameraId, largeIndex);
299 int smallIndex = getNextSupportedVideoQualityIndex(cameraId, mediumIndex);
Sascha Haeberling6ccec202014-03-11 09:44:34 -0700300
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;
Sascha Haeberlingde303232014-02-07 02:30:53 +0100307 }
308
309 /**
310 * Starting from 'start' this method returns the next supported video
311 * quality.
312 */
Andy Huibers81187932014-03-05 22:27:22 -0800313 private static int getNextSupportedVideoQualityIndex(int cameraId, int start) {
Paul Chang8d85af52014-03-24 12:05:45 -0400314 int i = start + 1;
Sascha Haeberlingde303232014-02-07 02:30:53 +0100315 for (; i < sVideoQualities.length; ++i) {
316 if (CamcorderProfile.hasProfile(cameraId, sVideoQualities[i])) {
Paul Chang8d85af52014-03-24 12:05:45 -0400317 // We found a new supported quality.
318 return i;
Sascha Haeberlingde303232014-02-07 02:30:53 +0100319 }
320 }
321
Paul Chang8d85af52014-03-24 12:05:45 -0400322 // 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.");
Sascha Haeberlingde303232014-02-07 02:30:53 +0100326 }
327
Paul Chang8d85af52014-03-24 12:05:45 -0400328 // We previously found a larger supported size. In this edge case, just
329 // return the same index as the previous size.
330 return start;
Sascha Haeberlingde303232014-02-07 02:30:53 +0100331 }
332
333 /**
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100334 * 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 }
Sascha Haeberlingde303232014-02-07 02:30:53 +0100351
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 }
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100429}