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