blob: 93613a1d6540a768d10d30818bc5dc0cd20d1bf3 [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 Kong2bca2102014-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 Kong2bca2102014-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 Kong2bca2102014-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.
Andy Huibers81187932014-03-05 22:27:22 -0800297 int largeIndex = getNextSupportedVideoQualityIndex(cameraId, 0);
Andy Huibers81187932014-03-05 22:27:22 -0800298 int mediumIndex = getNextSupportedVideoQualityIndex(cameraId, largeIndex + 1);
Andy Huibers81187932014-03-05 22:27:22 -0800299 int smallIndex = getNextSupportedVideoQualityIndex(cameraId, mediumIndex + 1);
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) {
Sascha Haeberlingde303232014-02-07 02:30:53 +0100314 int i = start;
315 for (; i < sVideoQualities.length; ++i) {
316 if (CamcorderProfile.hasProfile(cameraId, sVideoQualities[i])) {
317 break;
318 }
319 }
320
321 // Were we not able to find a supported quality?
322 if (i >= sVideoQualities.length) {
323 if (start == 0) {
324 // This means we couldn't find any supported quality.
325 throw new IllegalArgumentException("Could not find supported video qualities.");
326 } else {
327 // We get here if start is larger than zero then we found a
328 // larger size already previously. In this edge case, just
329 // return the same index as the previous size.
330 return start;
331 }
332 }
333
334 // We found a new supported quality.
335 return i;
336 }
337
338 /**
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100339 * Returns the index of the size within the given list that is closest to
340 * the given target pixel count.
341 */
342 private static int findClosestSize(List<Size> sortedSizes, int targetPixelCount) {
343 int closestMatchIndex = 0;
344 int closestMatchPixelCountDiff = Integer.MAX_VALUE;
345
346 for (int i = 0; i < sortedSizes.size(); ++i) {
347 Size size = sortedSizes.get(i);
348 int pixelCountDiff = Math.abs((size.width * size.height) - targetPixelCount);
349 if (pixelCountDiff < closestMatchPixelCountDiff) {
350 closestMatchIndex = i;
351 closestMatchPixelCountDiff = pixelCountDiff;
352 }
353 }
354 return closestMatchIndex;
355 }
Sascha Haeberlingde303232014-02-07 02:30:53 +0100356
357 /**
358 * Determines and returns the capabilities of the given camera.
359 */
360 public static SettingsCapabilities
361 getSettingsCapabilities(CameraManager.CameraProxy camera) {
362 final Parameters parameters = camera.getParameters();
363 return (new SettingsCapabilities() {
364 @Override
365 public String[] getSupportedExposureValues() {
366 int max = parameters.getMaxExposureCompensation();
367 int min = parameters.getMinExposureCompensation();
368 float step = parameters.getExposureCompensationStep();
369 int maxValue = Math.min(3, (int) Math.floor(max * step));
370 int minValue = Math.max(-3, (int) Math.ceil(min * step));
371 String[] entryValues = new String[maxValue - minValue + 1];
372 for (int i = minValue; i <= maxValue; ++i) {
373 entryValues[i - minValue] = Integer.toString(Math.round(i / step));
374 }
375 return entryValues;
376 }
377
378 @Override
379 public String[] getSupportedCameraIds() {
380 int numberOfCameras = Camera.getNumberOfCameras();
381 String[] cameraIds = new String[numberOfCameras];
382 for (int i = 0; i < numberOfCameras; i++) {
383 cameraIds[i] = "" + i;
384 }
385 return cameraIds;
386 }
387 });
388 }
389
390 /**
391 * Updates an AlertDialog.Builder to explain what it means to enable
392 * location on captures.
393 */
394 public static AlertDialog.Builder getFirstTimeLocationAlertBuilder(
395 AlertDialog.Builder builder, Callback<Boolean> callback) {
396 if (callback == null) {
397 return null;
398 }
399
400 getLocationAlertBuilder(builder, callback)
401 .setMessage(R.string.remember_location_prompt);
402
403 return builder;
404 }
405
406 /**
407 * Updates an AlertDialog.Builder for choosing whether to include location
408 * on captures.
409 */
410 public static AlertDialog.Builder getLocationAlertBuilder(AlertDialog.Builder builder,
411 final Callback<Boolean> callback) {
412 if (callback == null) {
413 return null;
414 }
415
416 builder.setTitle(R.string.remember_location_title)
417 .setPositiveButton(R.string.remember_location_yes,
418 new DialogInterface.OnClickListener() {
419 @Override
420 public void onClick(DialogInterface dialog, int arg1) {
421 callback.onCallback(true);
422 }
423 })
424 .setNegativeButton(R.string.remember_location_no,
425 new DialogInterface.OnClickListener() {
426 @Override
427 public void onClick(DialogInterface dialog, int arg1) {
428 callback.onCallback(false);
429 }
430 });
431
432 return builder;
433 }
Sascha Haeberling3b0ab892014-01-29 20:54:39 +0100434}