blob: fc324af1bc377bdf6365f36896274e4c8a9b93f3 [file] [log] [blame]
Michael Kolb8872c232013-01-29 10:33:22 -08001/*
2 * Copyright (C) 2009 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
Angus Kongb50b5cb2013-08-09 14:55:20 -070017package com.android.camera.util;
Michael Kolb8872c232013-01-29 10:33:22 -080018
Michael Kolb8872c232013-01-29 10:33:22 -080019import android.app.Activity;
20import android.app.AlertDialog;
21import android.app.admin.DevicePolicyManager;
22import android.content.ActivityNotFoundException;
Sascha Haeberlingfae11a12013-08-15 14:29:49 -070023import android.content.ComponentName;
Michael Kolb8872c232013-01-29 10:33:22 -080024import android.content.ContentResolver;
25import android.content.Context;
26import android.content.DialogInterface;
27import android.content.Intent;
Doris Liubd1b8f92014-01-03 17:59:51 -080028import android.content.res.TypedArray;
Michael Kolb8872c232013-01-29 10:33:22 -080029import android.graphics.Bitmap;
30import android.graphics.BitmapFactory;
31import android.graphics.Matrix;
32import android.graphics.Point;
33import android.graphics.Rect;
34import android.graphics.RectF;
35import android.hardware.Camera;
36import android.hardware.Camera.CameraInfo;
37import android.hardware.Camera.Parameters;
38import android.hardware.Camera.Size;
39import android.location.Location;
40import android.net.Uri;
Michael Kolb8872c232013-01-29 10:33:22 -080041import android.os.ParcelFileDescriptor;
42import android.telephony.TelephonyManager;
43import android.util.DisplayMetrics;
Michael Kolb8872c232013-01-29 10:33:22 -080044import android.util.Log;
45import android.util.TypedValue;
Doris Liu0ba8eaa2013-10-16 12:51:02 -070046import android.view.Display;
Michael Kolb8872c232013-01-29 10:33:22 -080047import android.view.OrientationEventListener;
48import android.view.Surface;
49import android.view.View;
50import android.view.WindowManager;
51import android.view.animation.AlphaAnimation;
52import android.view.animation.Animation;
Angus Kongb40738a2013-06-03 14:55:34 -070053import android.widget.Toast;
Michael Kolb8872c232013-01-29 10:33:22 -080054
Sascha Haeberlingb7639c62013-09-09 14:42:43 -070055import com.android.camera.CameraActivity;
Angus Kongb50b5cb2013-08-09 14:55:20 -070056import com.android.camera.CameraDisabledException;
Sascha Haeberling37f36112013-08-06 14:31:52 -070057import com.android.camera2.R;
Michael Kolb8872c232013-01-29 10:33:22 -080058
Sascha Haeberling638e6f02013-09-18 14:28:51 -070059import java.io.Closeable;
60import java.io.IOException;
61import java.lang.reflect.Method;
62import java.text.SimpleDateFormat;
63import java.util.Date;
64import java.util.List;
65import java.util.Locale;
66import java.util.StringTokenizer;
67
Michael Kolb8872c232013-01-29 10:33:22 -080068/**
69 * Collection of utility functions used in this package.
70 */
Angus Kongb50b5cb2013-08-09 14:55:20 -070071public class CameraUtil {
Michael Kolb8872c232013-01-29 10:33:22 -080072 private static final String TAG = "Util";
73
ztenghui16a35202013-09-23 11:35:36 -070074 // For calculate the best fps range for still image capture.
75 private final static int MAX_PREVIEW_FPS_TIMES_1000 = 400000;
76 private final static int PREFERRED_PREVIEW_FPS_TIMES_1000 = 30000;
77
Angus Kongb50b5cb2013-08-09 14:55:20 -070078 // For creating crop intents.
79 public static final String KEY_RETURN_DATA = "return-data";
80 public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked";
81
Michael Kolb8872c232013-01-29 10:33:22 -080082 // Orientation hysteresis amount used in rounding, in degrees
83 public static final int ORIENTATION_HYSTERESIS = 5;
84
85 public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW";
86 // See android.hardware.Camera.ACTION_NEW_PICTURE.
87 public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
88 // See android.hardware.Camera.ACTION_NEW_VIDEO.
89 public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
90
Dan Aminzadefca1c5e2013-08-14 17:36:56 -070091 // Broadcast Action: The camera application has become active in picture-taking mode.
92 public static final String ACTION_CAMERA_STARTED = "com.android.camera.action.CAMERA_STARTED";
93 // Broadcast Action: The camera application is no longer in active picture-taking mode.
94 public static final String ACTION_CAMERA_STOPPED = "com.android.camera.action.CAMERA_STOPPED";
95 // When the camera application is active in picture-taking mode, it listens for this intent,
96 // which upon receipt will trigger the shutter to capture a new picture, as if the user had
97 // pressed the shutter button.
98 public static final String ACTION_CAMERA_SHUTTER_CLICK =
99 "com.android.camera.action.SHUTTER_CLICK";
100
Michael Kolb8872c232013-01-29 10:33:22 -0800101 // Fields from android.hardware.Camera.Parameters
102 public static final String FOCUS_MODE_CONTINUOUS_PICTURE = "continuous-picture";
103 public static final String RECORDING_HINT = "recording-hint";
104 private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
105 private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported";
106 private static final String VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported";
107 public static final String SCENE_MODE_HDR = "hdr";
108 public static final String TRUE = "true";
109 public static final String FALSE = "false";
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700110
111 // Fields for the show-on-maps-functionality
112 private static final String MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
113 private static final String MAPS_CLASS_NAME = "com.google.android.maps.MapsActivity";
114
115 /** Has to be in sync with the receiving MovieActivity. */
116 public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back";
Michael Kolb8872c232013-01-29 10:33:22 -0800117
118 public static boolean isSupported(String value, List<String> supported) {
119 return supported == null ? false : supported.indexOf(value) >= 0;
120 }
121
122 public static boolean isAutoExposureLockSupported(Parameters params) {
123 return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
124 }
125
126 public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
127 return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
128 }
129
130 public static boolean isVideoSnapshotSupported(Parameters params) {
131 return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED));
132 }
133
134 public static boolean isCameraHdrSupported(Parameters params) {
135 List<String> supported = params.getSupportedSceneModes();
136 return (supported != null) && supported.contains(SCENE_MODE_HDR);
137 }
138
Michael Kolb8872c232013-01-29 10:33:22 -0800139 public static boolean isMeteringAreaSupported(Parameters params) {
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700140 return params.getMaxNumMeteringAreas() > 0;
Michael Kolb8872c232013-01-29 10:33:22 -0800141 }
142
Michael Kolb8872c232013-01-29 10:33:22 -0800143 public static boolean isFocusAreaSupported(Parameters params) {
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700144 return (params.getMaxNumFocusAreas() > 0
145 && isSupported(Parameters.FOCUS_MODE_AUTO,
146 params.getSupportedFocusModes()));
Michael Kolb8872c232013-01-29 10:33:22 -0800147 }
148
149 // Private intent extras. Test only.
150 private static final String EXTRAS_CAMERA_FACING =
151 "android.intent.extras.CAMERA_FACING";
152
153 private static float sPixelDensity = 1;
154 private static ImageFileNamer sImageFileNamer;
155
Angus Kongb50b5cb2013-08-09 14:55:20 -0700156 private CameraUtil() {
Michael Kolb8872c232013-01-29 10:33:22 -0800157 }
158
159 public static void initialize(Context context) {
160 DisplayMetrics metrics = new DisplayMetrics();
161 WindowManager wm = (WindowManager)
162 context.getSystemService(Context.WINDOW_SERVICE);
163 wm.getDefaultDisplay().getMetrics(metrics);
164 sPixelDensity = metrics.density;
165 sImageFileNamer = new ImageFileNamer(
166 context.getString(R.string.image_file_name_format));
167 }
168
169 public static int dpToPixel(int dp) {
170 return Math.round(sPixelDensity * dp);
171 }
172
173 // Rotates the bitmap by the specified degree.
174 // If a new bitmap is created, the original bitmap is recycled.
175 public static Bitmap rotate(Bitmap b, int degrees) {
176 return rotateAndMirror(b, degrees, false);
177 }
178
179 // Rotates and/or mirrors the bitmap. If a new bitmap is created, the
180 // original bitmap is recycled.
181 public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) {
182 if ((degrees != 0 || mirror) && b != null) {
183 Matrix m = new Matrix();
184 // Mirror first.
185 // horizontal flip + rotation = -rotation + horizontal flip
186 if (mirror) {
187 m.postScale(-1, 1);
188 degrees = (degrees + 360) % 360;
189 if (degrees == 0 || degrees == 180) {
190 m.postTranslate(b.getWidth(), 0);
191 } else if (degrees == 90 || degrees == 270) {
192 m.postTranslate(b.getHeight(), 0);
193 } else {
194 throw new IllegalArgumentException("Invalid degrees=" + degrees);
195 }
196 }
197 if (degrees != 0) {
198 // clockwise
199 m.postRotate(degrees,
200 (float) b.getWidth() / 2, (float) b.getHeight() / 2);
201 }
202
203 try {
204 Bitmap b2 = Bitmap.createBitmap(
205 b, 0, 0, b.getWidth(), b.getHeight(), m, true);
206 if (b != b2) {
207 b.recycle();
208 b = b2;
209 }
210 } catch (OutOfMemoryError ex) {
211 // We have no memory to rotate. Return the original bitmap.
212 }
213 }
214 return b;
215 }
216
217 /*
218 * Compute the sample size as a function of minSideLength
219 * and maxNumOfPixels.
220 * minSideLength is used to specify that minimal width or height of a
221 * bitmap.
222 * maxNumOfPixels is used to specify the maximal size in pixels that is
223 * tolerable in terms of memory usage.
224 *
225 * The function returns a sample size based on the constraints.
226 * Both size and minSideLength can be passed in as -1
227 * which indicates no care of the corresponding constraint.
228 * The functions prefers returning a sample size that
229 * generates a smaller bitmap, unless minSideLength = -1.
230 *
231 * Also, the function rounds up the sample size to a power of 2 or multiple
232 * of 8 because BitmapFactory only honors sample size this way.
233 * For example, BitmapFactory downsamples an image by 2 even though the
234 * request is 3. So we round up the sample size to avoid OOM.
235 */
236 public static int computeSampleSize(BitmapFactory.Options options,
237 int minSideLength, int maxNumOfPixels) {
238 int initialSize = computeInitialSampleSize(options, minSideLength,
239 maxNumOfPixels);
240
241 int roundedSize;
242 if (initialSize <= 8) {
243 roundedSize = 1;
244 while (roundedSize < initialSize) {
245 roundedSize <<= 1;
246 }
247 } else {
248 roundedSize = (initialSize + 7) / 8 * 8;
249 }
250
251 return roundedSize;
252 }
253
254 private static int computeInitialSampleSize(BitmapFactory.Options options,
255 int minSideLength, int maxNumOfPixels) {
256 double w = options.outWidth;
257 double h = options.outHeight;
258
259 int lowerBound = (maxNumOfPixels < 0) ? 1 :
260 (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
261 int upperBound = (minSideLength < 0) ? 128 :
262 (int) Math.min(Math.floor(w / minSideLength),
263 Math.floor(h / minSideLength));
264
265 if (upperBound < lowerBound) {
266 // return the larger one when there is no overlapping zone.
267 return lowerBound;
268 }
269
270 if (maxNumOfPixels < 0 && minSideLength < 0) {
271 return 1;
272 } else if (minSideLength < 0) {
273 return lowerBound;
274 } else {
275 return upperBound;
276 }
277 }
278
279 public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) {
280 try {
281 BitmapFactory.Options options = new BitmapFactory.Options();
282 options.inJustDecodeBounds = true;
283 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
284 options);
285 if (options.mCancel || options.outWidth == -1
286 || options.outHeight == -1) {
287 return null;
288 }
289 options.inSampleSize = computeSampleSize(
290 options, -1, maxNumOfPixels);
291 options.inJustDecodeBounds = false;
292
293 options.inDither = false;
294 options.inPreferredConfig = Bitmap.Config.ARGB_8888;
295 return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
296 options);
297 } catch (OutOfMemoryError ex) {
298 Log.e(TAG, "Got oom exception ", ex);
299 return null;
300 }
301 }
302
303 public static void closeSilently(Closeable c) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100304 if (c == null) {
305 return;
306 }
Michael Kolb8872c232013-01-29 10:33:22 -0800307 try {
308 c.close();
309 } catch (Throwable t) {
310 // do nothing
311 }
312 }
313
314 public static void Assert(boolean cond) {
315 if (!cond) {
316 throw new AssertionError();
317 }
318 }
319
Michael Kolb8872c232013-01-29 10:33:22 -0800320 private static void throwIfCameraDisabled(Activity activity) throws CameraDisabledException {
321 // Check if device policy has disabled the camera.
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700322 DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService(
323 Context.DEVICE_POLICY_SERVICE);
324 if (dpm.getCameraDisabled(null)) {
325 throw new CameraDisabledException();
Michael Kolb8872c232013-01-29 10:33:22 -0800326 }
327 }
328
Michael Kolb8872c232013-01-29 10:33:22 -0800329 public static void showErrorAndFinish(final Activity activity, int msgId) {
330 DialogInterface.OnClickListener buttonListener =
331 new DialogInterface.OnClickListener() {
332 @Override
333 public void onClick(DialogInterface dialog, int which) {
334 activity.finish();
335 }
336 };
337 TypedValue out = new TypedValue();
338 activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true);
339 new AlertDialog.Builder(activity)
340 .setCancelable(false)
341 .setTitle(R.string.camera_error_title)
342 .setMessage(msgId)
343 .setNeutralButton(R.string.dialog_ok, buttonListener)
344 .setIcon(out.resourceId)
345 .show();
346 }
347
348 public static <T> T checkNotNull(T object) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100349 if (object == null) {
350 throw new NullPointerException();
351 }
Michael Kolb8872c232013-01-29 10:33:22 -0800352 return object;
353 }
354
355 public static boolean equals(Object a, Object b) {
356 return (a == b) || (a == null ? false : a.equals(b));
357 }
358
359 public static int nextPowerOf2(int n) {
360 n -= 1;
361 n |= n >>> 16;
362 n |= n >>> 8;
363 n |= n >>> 4;
364 n |= n >>> 2;
365 n |= n >>> 1;
366 return n + 1;
367 }
368
369 public static float distance(float x, float y, float sx, float sy) {
370 float dx = x - sx;
371 float dy = y - sy;
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700372 return (float) Math.sqrt(dx * dx + dy * dy);
Michael Kolb8872c232013-01-29 10:33:22 -0800373 }
374
375 public static int clamp(int x, int min, int max) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100376 if (x > max) {
377 return max;
378 }
379 if (x < min) {
380 return min;
381 }
Michael Kolb8872c232013-01-29 10:33:22 -0800382 return x;
383 }
384
Igor Murashkin160b0362013-10-14 14:57:30 -0700385 public static float clamp(float x, float min, float max) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100386 if (x > max) {
387 return max;
388 }
389 if (x < min) {
390 return min;
391 }
Igor Murashkin160b0362013-10-14 14:57:30 -0700392 return x;
393 }
394
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100395 public static int getDisplayRotation(Context context) {
396 WindowManager windowManager = (WindowManager) context
397 .getSystemService(Context.WINDOW_SERVICE);
398 int rotation = windowManager.getDefaultDisplay()
Michael Kolb8872c232013-01-29 10:33:22 -0800399 .getRotation();
400 switch (rotation) {
401 case Surface.ROTATION_0: return 0;
402 case Surface.ROTATION_90: return 90;
403 case Surface.ROTATION_180: return 180;
404 case Surface.ROTATION_270: return 270;
405 }
406 return 0;
407 }
408
Doris Liu0ba8eaa2013-10-16 12:51:02 -0700409 /**
410 * Calculate the default orientation of the device based on the width and
411 * height of the display when rotation = 0 (i.e. natural width and height)
412 * @param activity the activity context
413 * @return whether the default orientation of the device is portrait
414 */
415 public static boolean isDefaultToPortrait(Activity activity) {
416 Display currentDisplay = activity.getWindowManager().getDefaultDisplay();
417 Point displaySize = new Point();
418 currentDisplay.getSize(displaySize);
419 int orientation = currentDisplay.getRotation();
420 int naturalWidth, naturalHeight;
421 if (orientation == Surface.ROTATION_0 || orientation == Surface.ROTATION_180) {
422 naturalWidth = displaySize.x;
423 naturalHeight = displaySize.y;
424 } else {
425 naturalWidth = displaySize.y;
426 naturalHeight = displaySize.x;
427 }
428 return naturalWidth < naturalHeight;
429 }
430
Michael Kolb8872c232013-01-29 10:33:22 -0800431 public static int getDisplayOrientation(int degrees, int cameraId) {
432 // See android.hardware.Camera.setDisplayOrientation for
433 // documentation.
434 Camera.CameraInfo info = new Camera.CameraInfo();
435 Camera.getCameraInfo(cameraId, info);
436 int result;
437 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
438 result = (info.orientation + degrees) % 360;
439 result = (360 - result) % 360; // compensate the mirror
440 } else { // back-facing
441 result = (info.orientation - degrees + 360) % 360;
442 }
443 return result;
444 }
445
446 public static int getCameraOrientation(int cameraId) {
447 Camera.CameraInfo info = new Camera.CameraInfo();
448 Camera.getCameraInfo(cameraId, info);
449 return info.orientation;
450 }
451
452 public static int roundOrientation(int orientation, int orientationHistory) {
453 boolean changeOrientation = false;
454 if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
455 changeOrientation = true;
456 } else {
457 int dist = Math.abs(orientation - orientationHistory);
458 dist = Math.min( dist, 360 - dist );
459 changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS );
460 }
461 if (changeOrientation) {
462 return ((orientation + 45) / 90 * 90) % 360;
463 }
464 return orientationHistory;
465 }
466
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100467 private static Point getDefaultDisplaySize(Context context, Point size) {
468 WindowManager windowManager = (WindowManager) context
469 .getSystemService(Context.WINDOW_SERVICE);
470 windowManager.getDefaultDisplay().getSize(size);
Michael Kolb8872c232013-01-29 10:33:22 -0800471 return size;
472 }
473
474 public static Size getOptimalPreviewSize(Activity currentActivity,
475 List<Size> sizes, double targetRatio) {
Michael Kolb8872c232013-01-29 10:33:22 -0800476
Ruben Brunk714d4d02013-10-09 16:01:16 -0700477 Point[] points = new Point[sizes.size()];
478
479 int index = 0;
480 for (Size s : sizes) {
481 points[index++] = new Point(s.width, s.height);
482 }
483
484 int optimalPickIndex = getOptimalPreviewSize(currentActivity, points, targetRatio);
485 return (optimalPickIndex == -1) ? null : sizes.get(optimalPickIndex);
486 }
487
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100488 public static int getOptimalPreviewSize(Context context,
Ruben Brunk714d4d02013-10-09 16:01:16 -0700489 Point[] sizes, double targetRatio) {
490 // Use a very small tolerance because we want an exact match.
491 final double ASPECT_TOLERANCE = 0.01;
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100492 if (sizes == null) {
493 return -1;
494 }
Ruben Brunk714d4d02013-10-09 16:01:16 -0700495
496 int optimalSizeIndex = -1;
Michael Kolb8872c232013-01-29 10:33:22 -0800497 double minDiff = Double.MAX_VALUE;
498
499 // Because of bugs of overlay and layout, we sometimes will try to
500 // layout the viewfinder in the portrait orientation and thus get the
501 // wrong size of preview surface. When we change the preview size, the
502 // new overlay will be created before the old one closed, which causes
503 // an exception. For now, just get the screen size.
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100504 Point point = getDefaultDisplaySize(context, new Point());
Michael Kolb8872c232013-01-29 10:33:22 -0800505 int targetHeight = Math.min(point.x, point.y);
506 // Try to find an size match aspect ratio and size
Ruben Brunk714d4d02013-10-09 16:01:16 -0700507 for (int i = 0; i < sizes.length; i++) {
508 Point size = sizes[i];
509 double ratio = (double) size.x / size.y;
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100510 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) {
511 continue;
512 }
Ruben Brunk714d4d02013-10-09 16:01:16 -0700513 if (Math.abs(size.y - targetHeight) < minDiff) {
514 optimalSizeIndex = i;
515 minDiff = Math.abs(size.y - targetHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800516 }
517 }
518 // Cannot find the one match the aspect ratio. This should not happen.
519 // Ignore the requirement.
Ruben Brunk714d4d02013-10-09 16:01:16 -0700520 if (optimalSizeIndex == -1) {
Michael Kolb8872c232013-01-29 10:33:22 -0800521 Log.w(TAG, "No preview size match the aspect ratio");
522 minDiff = Double.MAX_VALUE;
Ruben Brunk714d4d02013-10-09 16:01:16 -0700523 for (int i = 0; i < sizes.length; i++) {
524 Point size = sizes[i];
525 if (Math.abs(size.y - targetHeight) < minDiff) {
526 optimalSizeIndex = i;
527 minDiff = Math.abs(size.y - targetHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800528 }
529 }
530 }
Ruben Brunk714d4d02013-10-09 16:01:16 -0700531 return optimalSizeIndex;
Michael Kolb8872c232013-01-29 10:33:22 -0800532 }
533
534 // Returns the largest picture size which matches the given aspect ratio.
535 public static Size getOptimalVideoSnapshotPictureSize(
536 List<Size> sizes, double targetRatio) {
537 // Use a very small tolerance because we want an exact match.
538 final double ASPECT_TOLERANCE = 0.001;
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100539 if (sizes == null) {
540 return null;
541 }
Michael Kolb8872c232013-01-29 10:33:22 -0800542
543 Size optimalSize = null;
544
545 // Try to find a size matches aspect ratio and has the largest width
546 for (Size size : sizes) {
547 double ratio = (double) size.width / size.height;
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100548 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) {
549 continue;
550 }
Michael Kolb8872c232013-01-29 10:33:22 -0800551 if (optimalSize == null || size.width > optimalSize.width) {
552 optimalSize = size;
553 }
554 }
555
556 // Cannot find one that matches the aspect ratio. This should not happen.
557 // Ignore the requirement.
558 if (optimalSize == null) {
559 Log.w(TAG, "No picture size match the aspect ratio");
560 for (Size size : sizes) {
561 if (optimalSize == null || size.width > optimalSize.width) {
562 optimalSize = size;
563 }
564 }
565 }
566 return optimalSize;
567 }
568
569 public static void dumpParameters(Parameters parameters) {
570 String flattened = parameters.flatten();
571 StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
572 Log.d(TAG, "Dump all camera parameters:");
573 while (tokenizer.hasMoreElements()) {
574 Log.d(TAG, tokenizer.nextToken());
575 }
576 }
577
578 /**
579 * Returns whether the device is voice-capable (meaning, it can do MMS).
580 */
581 public static boolean isMmsCapable(Context context) {
582 TelephonyManager telephonyManager = (TelephonyManager)
583 context.getSystemService(Context.TELEPHONY_SERVICE);
584 if (telephonyManager == null) {
585 return false;
586 }
587
588 try {
589 Class<?> partypes[] = new Class[0];
590 Method sIsVoiceCapable = TelephonyManager.class.getMethod(
591 "isVoiceCapable", partypes);
592
593 Object arglist[] = new Object[0];
594 Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist);
595 return (Boolean) retobj;
596 } catch (java.lang.reflect.InvocationTargetException ite) {
597 // Failure, must be another device.
598 // Assume that it is voice capable.
599 } catch (IllegalAccessException iae) {
600 // Failure, must be an other device.
601 // Assume that it is voice capable.
602 } catch (NoSuchMethodException nsme) {
603 }
604 return true;
605 }
606
607 // This is for test only. Allow the camera to launch the specific camera.
608 public static int getCameraFacingIntentExtras(Activity currentActivity) {
609 int cameraId = -1;
610
611 int intentCameraId =
Angus Kongb50b5cb2013-08-09 14:55:20 -0700612 currentActivity.getIntent().getIntExtra(CameraUtil.EXTRAS_CAMERA_FACING, -1);
Michael Kolb8872c232013-01-29 10:33:22 -0800613
614 if (isFrontCameraIntent(intentCameraId)) {
615 // Check if the front camera exist
Angus Kong20fad242013-11-11 18:23:46 -0800616 int frontCameraId = ((CameraActivity) currentActivity).getCameraProvider()
617 .getFirstFrontCameraId();
Michael Kolb8872c232013-01-29 10:33:22 -0800618 if (frontCameraId != -1) {
619 cameraId = frontCameraId;
620 }
621 } else if (isBackCameraIntent(intentCameraId)) {
622 // Check if the back camera exist
Angus Kong20fad242013-11-11 18:23:46 -0800623 int backCameraId = ((CameraActivity) currentActivity).getCameraProvider()
624 .getFirstBackCameraId();
Michael Kolb8872c232013-01-29 10:33:22 -0800625 if (backCameraId != -1) {
626 cameraId = backCameraId;
627 }
628 }
629 return cameraId;
630 }
631
632 private static boolean isFrontCameraIntent(int intentCameraId) {
633 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
634 }
635
636 private static boolean isBackCameraIntent(int intentCameraId) {
637 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
638 }
639
640 private static int sLocation[] = new int[2];
641
642 // This method is not thread-safe.
643 public static boolean pointInView(float x, float y, View v) {
644 v.getLocationInWindow(sLocation);
645 return x >= sLocation[0] && x < (sLocation[0] + v.getWidth())
646 && y >= sLocation[1] && y < (sLocation[1] + v.getHeight());
647 }
648
649 public static int[] getRelativeLocation(View reference, View view) {
650 reference.getLocationInWindow(sLocation);
651 int referenceX = sLocation[0];
652 int referenceY = sLocation[1];
653 view.getLocationInWindow(sLocation);
654 sLocation[0] -= referenceX;
655 sLocation[1] -= referenceY;
656 return sLocation;
657 }
658
659 public static boolean isUriValid(Uri uri, ContentResolver resolver) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100660 if (uri == null) {
661 return false;
662 }
Michael Kolb8872c232013-01-29 10:33:22 -0800663
664 try {
665 ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
666 if (pfd == null) {
667 Log.e(TAG, "Fail to open URI. URI=" + uri);
668 return false;
669 }
670 pfd.close();
671 } catch (IOException ex) {
672 return false;
673 }
674 return true;
675 }
676
Michael Kolb8872c232013-01-29 10:33:22 -0800677 public static void dumpRect(RectF rect, String msg) {
678 Log.v(TAG, msg + "=(" + rect.left + "," + rect.top
679 + "," + rect.right + "," + rect.bottom + ")");
680 }
681
682 public static void rectFToRect(RectF rectF, Rect rect) {
683 rect.left = Math.round(rectF.left);
684 rect.top = Math.round(rectF.top);
685 rect.right = Math.round(rectF.right);
686 rect.bottom = Math.round(rectF.bottom);
687 }
688
Doris Liu36ebcb12013-10-28 14:44:24 -0700689 public static Rect rectFToRect(RectF rectF) {
690 Rect rect = new Rect();
691 rectFToRect(rectF, rect);
692 return rect;
693 }
694
695 public static RectF rectToRectF(Rect r) {
696 return new RectF(r.left, r.top, r.right, r.bottom);
697 }
698
Michael Kolb8872c232013-01-29 10:33:22 -0800699 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
700 int viewWidth, int viewHeight) {
701 // Need mirror for front camera.
702 matrix.setScale(mirror ? -1 : 1, 1);
703 // This is the value for android.hardware.Camera.setDisplayOrientation.
704 matrix.postRotate(displayOrientation);
705 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
706 // UI coordinates range from (0, 0) to (width, height).
707 matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
708 matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
709 }
710
Doris Liu36ebcb12013-10-28 14:44:24 -0700711 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
712 Rect previewRect) {
713 // Need mirror for front camera.
714 matrix.setScale(mirror ? -1 : 1, 1);
715 // This is the value for android.hardware.Camera.setDisplayOrientation.
716 matrix.postRotate(displayOrientation);
717
718 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
719 // We need to map camera driver coordinates to preview rect coordinates
720 Matrix mapping = new Matrix();
721 mapping.setRectToRect(new RectF(-1000, -1000, 1000, 1000), rectToRectF(previewRect),
722 Matrix.ScaleToFit.FILL);
723 matrix.setConcat(mapping, matrix);
724 }
725
Michael Kolb8872c232013-01-29 10:33:22 -0800726 public static String createJpegName(long dateTaken) {
727 synchronized (sImageFileNamer) {
728 return sImageFileNamer.generateName(dateTaken);
729 }
730 }
731
732 public static void broadcastNewPicture(Context context, Uri uri) {
733 context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri));
734 // Keep compatibility
735 context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri));
736 }
737
738 public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100739 if (view.getVisibility() == View.VISIBLE) {
740 return;
741 }
Michael Kolb8872c232013-01-29 10:33:22 -0800742
743 view.setVisibility(View.VISIBLE);
744 Animation animation = new AlphaAnimation(startAlpha, endAlpha);
745 animation.setDuration(duration);
746 view.startAnimation(animation);
747 }
748
749 public static void fadeIn(View view) {
750 fadeIn(view, 0F, 1F, 400);
751
752 // We disabled the button in fadeOut(), so enable it here.
753 view.setEnabled(true);
754 }
755
756 public static void fadeOut(View view) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100757 if (view.getVisibility() != View.VISIBLE) {
758 return;
759 }
Michael Kolb8872c232013-01-29 10:33:22 -0800760
761 // Since the button is still clickable before fade-out animation
762 // ends, we disable the button first to block click.
763 view.setEnabled(false);
764 Animation animation = new AlphaAnimation(1F, 0F);
765 animation.setDuration(400);
766 view.startAnimation(animation);
767 view.setVisibility(View.GONE);
768 }
769
Angus Kong20fad242013-11-11 18:23:46 -0800770 public static int getJpegRotation(CameraActivity activity, int cameraId, int orientation) {
Michael Kolb8872c232013-01-29 10:33:22 -0800771 // See android.hardware.Camera.Parameters.setRotation for
772 // documentation.
773 int rotation = 0;
774 if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
Angus Kong20fad242013-11-11 18:23:46 -0800775 CameraInfo info = activity.getCameraProvider().getCameraInfo()[cameraId];
Michael Kolb8872c232013-01-29 10:33:22 -0800776 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
777 rotation = (info.orientation - orientation + 360) % 360;
778 } else { // back-facing camera
779 rotation = (info.orientation + orientation) % 360;
780 }
781 }
782 return rotation;
783 }
784
Sascha Haeberling37f36112013-08-06 14:31:52 -0700785 /**
786 * Down-samples a jpeg byte array.
787 * @param data a byte array of jpeg data
788 * @param downSampleFactor down-sample factor
789 * @return decoded and down-sampled bitmap
790 */
791 public static Bitmap downSample(final byte[] data, int downSampleFactor) {
792 final BitmapFactory.Options opts = new BitmapFactory.Options();
793 // Downsample the image
794 opts.inSampleSize = downSampleFactor;
795 return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
796 }
797
Michael Kolb8872c232013-01-29 10:33:22 -0800798 public static void setGpsParameters(Parameters parameters, Location loc) {
799 // Clear previous GPS location from the parameters.
800 parameters.removeGpsData();
801
802 // We always encode GpsTimeStamp
803 parameters.setGpsTimestamp(System.currentTimeMillis() / 1000);
804
805 // Set GPS location.
806 if (loc != null) {
807 double lat = loc.getLatitude();
808 double lon = loc.getLongitude();
809 boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
810
811 if (hasLatLon) {
812 Log.d(TAG, "Set gps location");
813 parameters.setGpsLatitude(lat);
814 parameters.setGpsLongitude(lon);
815 parameters.setGpsProcessingMethod(loc.getProvider().toUpperCase());
816 if (loc.hasAltitude()) {
817 parameters.setGpsAltitude(loc.getAltitude());
818 } else {
819 // for NETWORK_PROVIDER location provider, we may have
820 // no altitude information, but the driver needs it, so
821 // we fake one.
822 parameters.setGpsAltitude(0);
823 }
824 if (loc.getTime() != 0) {
825 // Location.getTime() is UTC in milliseconds.
826 // gps-timestamp is UTC in seconds.
827 long utcTimeSeconds = loc.getTime() / 1000;
828 parameters.setGpsTimestamp(utcTimeSeconds);
829 }
830 } else {
831 loc = null;
832 }
833 }
834 }
835
ztenghui16a35202013-09-23 11:35:36 -0700836 /**
837 * For still image capture, we need to get the right fps range such that the
838 * camera can slow down the framerate to allow for less-noisy/dark
839 * viewfinder output in dark conditions.
840 *
841 * @param params Camera's parameters.
842 * @return null if no appropiate fps range can't be found. Otherwise, return
843 * the right range.
844 */
845 public static int[] getPhotoPreviewFpsRange(Parameters params) {
Ruben Brunk2a74dfd2013-10-29 18:28:32 -0700846 return getPhotoPreviewFpsRange(params.getSupportedPreviewFpsRange());
847 }
848
849 public static int[] getPhotoPreviewFpsRange(List<int[]> frameRates) {
ztenghui16a35202013-09-23 11:35:36 -0700850 if (frameRates.size() == 0) {
851 Log.e(TAG, "No suppoted frame rates returned!");
852 return null;
853 }
854
855 // Find the lowest min rate in supported ranges who can cover 30fps.
856 int lowestMinRate = MAX_PREVIEW_FPS_TIMES_1000;
857 for (int[] rate : frameRates) {
858 int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX];
859 int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX];
860 if (maxFps >= PREFERRED_PREVIEW_FPS_TIMES_1000 &&
861 minFps <= PREFERRED_PREVIEW_FPS_TIMES_1000 &&
862 minFps < lowestMinRate) {
863 lowestMinRate = minFps;
864 }
865 }
866
867 // Find all the modes with the lowest min rate found above, the pick the
868 // one with highest max rate.
869 int resultIndex = -1;
870 int highestMaxRate = 0;
871 for (int i = 0; i < frameRates.size(); i++) {
872 int[] rate = frameRates.get(i);
873 int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX];
874 int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX];
875 if (minFps == lowestMinRate && highestMaxRate < maxFps) {
876 highestMaxRate = maxFps;
877 resultIndex = i;
878 }
879 }
880
881 if (resultIndex >= 0) {
882 return frameRates.get(resultIndex);
883 }
884 Log.e(TAG, "Can't find an appropiate frame rate range!");
885 return null;
886 }
Angus Kongd82eae22013-06-11 12:00:15 -0700887
888 public static int[] getMaxPreviewFpsRange(Parameters params) {
889 List<int[]> frameRates = params.getSupportedPreviewFpsRange();
890 if (frameRates != null && frameRates.size() > 0) {
891 // The list is sorted. Return the last element.
892 return frameRates.get(frameRates.size() - 1);
893 }
894 return new int[0];
895 }
896
Angus Kong20fad242013-11-11 18:23:46 -0800897 public static void throwIfCameraDisabled(Context context) throws CameraDisabledException {
898 // Check if device policy has disabled the camera.
899 DevicePolicyManager dpm =
900 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
901 if (dpm.getCameraDisabled(null)) {
902 throw new CameraDisabledException();
903 }
904 }
905
Michael Kolb8872c232013-01-29 10:33:22 -0800906 private static class ImageFileNamer {
Igor Murashkin160b0362013-10-14 14:57:30 -0700907 private final SimpleDateFormat mFormat;
Michael Kolb8872c232013-01-29 10:33:22 -0800908
909 // The date (in milliseconds) used to generate the last name.
910 private long mLastDate;
911
912 // Number of names generated for the same second.
913 private int mSameSecondCount;
914
915 public ImageFileNamer(String format) {
916 mFormat = new SimpleDateFormat(format);
917 }
918
919 public String generateName(long dateTaken) {
920 Date date = new Date(dateTaken);
921 String result = mFormat.format(date);
922
923 // If the last name was generated for the same second,
924 // we append _1, _2, etc to the name.
925 if (dateTaken / 1000 == mLastDate / 1000) {
926 mSameSecondCount++;
927 result += "_" + mSameSecondCount;
928 } else {
929 mLastDate = dateTaken;
930 mSameSecondCount = 0;
931 }
932
933 return result;
934 }
935 }
Angus Kongb40738a2013-06-03 14:55:34 -0700936
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700937 public static void playVideo(Activity activity, Uri uri, String title) {
Angus Kongb40738a2013-06-03 14:55:34 -0700938 try {
ztenghui98b2a282013-10-11 17:15:40 -0700939 boolean isSecureCamera = ((CameraActivity)activity).isSecureCamera();
940 if (!isSecureCamera) {
Mangesh Ghiware2d939fe2013-10-14 21:19:22 -0700941 Intent intent = IntentHelper.getVideoPlayerIntent(activity, uri)
ztenghui98b2a282013-10-11 17:15:40 -0700942 .putExtra(Intent.EXTRA_TITLE, title)
943 .putExtra(KEY_TREAT_UP_AS_BACK, true);
944 activity.startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
945 } else {
946 // In order not to send out any intent to be intercepted and
947 // show the lock screen immediately, we just let the secure
948 // camera activity finish.
949 activity.finish();
950 }
Angus Kongb40738a2013-06-03 14:55:34 -0700951 } catch (ActivityNotFoundException e) {
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700952 Toast.makeText(activity, activity.getString(R.string.video_err),
Angus Kongb40738a2013-06-03 14:55:34 -0700953 Toast.LENGTH_SHORT).show();
954 }
955 }
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700956
957 /**
958 * Starts GMM with the given location shown. If this fails, and GMM could
959 * not be found, we use a geo intent as a fallback.
960 *
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700961 * @param activity the activity to use for launching the Maps intent.
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700962 * @param latLong a 2-element array containing {latitude/longitude}.
963 */
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700964 public static void showOnMap(Activity activity, double[] latLong) {
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700965 try {
966 // We don't use "geo:latitude,longitude" because it only centers
967 // the MapView to the specified location, but we need a marker
968 // for further operations (routing to/from).
969 // The q=(lat, lng) syntax is suggested by geo-team.
970 String uri = String.format(Locale.ENGLISH, "http://maps.google.com/maps?f=q&q=(%f,%f)",
971 latLong[0], latLong[1]);
972 ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME,
973 MAPS_CLASS_NAME);
974 Intent mapsIntent = new Intent(Intent.ACTION_VIEW,
975 Uri.parse(uri)).setComponent(compName);
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700976 activity.startActivityForResult(mapsIntent,
977 CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700978 } catch (ActivityNotFoundException e) {
979 // Use the "geo intent" if no GMM is installed
980 Log.e(TAG, "GMM activity not found!", e);
981 String url = String.format(Locale.ENGLISH, "geo:%f,%f", latLong[0], latLong[1]);
982 Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700983 activity.startActivity(mapsIntent);
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700984 }
985 }
Angus Kong7d2388d2013-09-18 23:56:01 -0700986
Angus Kongb21215a2013-09-20 18:41:46 -0700987 /**
988 * Dumps the stack trace.
989 *
990 * @param level How many levels of the stack are dumped. 0 means all.
991 * @return A {@link java.lang.String} of all the output with newline
992 * between each.
993 */
Angus Kong7d2388d2013-09-18 23:56:01 -0700994 public static String dumpStackTrace(int level) {
995 StackTraceElement[] elems = Thread.currentThread().getStackTrace();
996 // Ignore the first 3 elements.
Angus Kongb21215a2013-09-20 18:41:46 -0700997 level = (level == 0 ? elems.length : Math.min(level + 3, elems.length));
Angus Kong7d2388d2013-09-18 23:56:01 -0700998 String ret = new String();
999 for (int i = 3; i < level; i++) {
Angus Kongb21215a2013-09-20 18:41:46 -07001000 ret = ret + "\t" + elems[i].toString() + '\n';
Angus Kong7d2388d2013-09-18 23:56:01 -07001001 }
1002 return ret;
1003 }
Doris Liubd1b8f92014-01-03 17:59:51 -08001004
1005
1006 /**
1007 * Gets the theme color of a specific mode.
1008 *
1009 * @param modeIndex index of the mode
1010 * @param context current context
1011 * @return theme color of the mode if input index is valid, otherwise 0
1012 */
1013 public static int getCameraThemeColorId(int modeIndex, Context context) {
1014
1015 // Find the theme color using id from the color array
1016 TypedArray colorRes = context.getResources()
1017 .obtainTypedArray(R.array.camera_mode_theme_color);
1018 if (modeIndex >= colorRes.length() || modeIndex < 0) {
1019 // Mode index not found
1020 Log.e(TAG, "Invalid mode index: " + modeIndex);
1021 return 0;
1022 }
1023 return colorRes.getResourceId(modeIndex, 0);
1024 }
1025
1026 /**
1027 * Gets the mode icon resource id of a specific mode.
1028 *
1029 * @param modeIndex index of the mode
1030 * @param context current context
1031 * @return icon resource id if the index is valid, otherwise 0
1032 */
1033 public static int getCameraModeIconResId(int modeIndex, Context context) {
1034 // Find the camera mode icon using id
1035 TypedArray cameraModesIcons = context.getResources()
1036 .obtainTypedArray(R.array.camera_mode_icon);
1037 if (modeIndex >= cameraModesIcons.length() || modeIndex < 0) {
1038 // Mode index not found
1039 Log.e(TAG, "Invalid mode index: " + modeIndex);
1040 return 0;
1041 }
1042 return cameraModesIcons.getResourceId(modeIndex, 0);
1043 }
1044
1045 /**
1046 * Gets the mode text of a specific mode.
1047 *
1048 * @param modeIndex index of the mode
1049 * @param context current context
1050 * @return mode text if the index is valid, otherwise a new empty string
1051 */
1052 public static String getCameraModeText(int modeIndex, Context context) {
1053 // Find the camera mode icon using id
1054 String[] cameraModesText = context.getResources()
1055 .getStringArray(R.array.camera_mode_text);
1056 if (modeIndex < 0 || modeIndex >= cameraModesText.length) {
1057 Log.e(TAG, "Invalid mode index: " + modeIndex);
1058 return new String();
1059 }
1060 return cameraModesText[modeIndex];
1061 }
Erin Dahlgrenfecd7232014-01-16 13:29:42 -08001062
1063 /**
Spike Sprague79718f62014-01-28 18:48:22 -08001064 * Gets the mode content description of a specific mode.
1065 *
1066 * @param modeIndex index of the mode
1067 * @param context current context
1068 * @return mode content description if the index is valid, otherwise a new empty string
1069 */
1070 public static String getCameraModeContentDescription(int modeIndex, Context context) {
1071 String[] cameraModesDesc = context.getResources()
1072 .getStringArray(R.array.camera_mode_content_description);
1073 if (modeIndex < 0 || modeIndex >= cameraModesDesc.length) {
1074 Log.e(TAG, "Invalid mode index: " + modeIndex);
1075 return new String();
1076 }
1077 return cameraModesDesc[modeIndex];
1078 }
1079
1080 /**
Erin Dahlgrenfecd7232014-01-16 13:29:42 -08001081 * Gets the shutter icon res id for a specific mode.
1082 *
1083 * @param modeIndex index of the mode
1084 * @param context current context
1085 * @return mode shutter icon id if the index is valid, otherwise 0.
1086 */
1087 public static int getCameraShutterIconId(int modeIndex, Context context) {
1088 // Find the camera mode icon using id
1089 TypedArray shutterIcons = context.getResources()
1090 .obtainTypedArray(R.array.camera_mode_shutter_icon);
1091 if (modeIndex < 0 || modeIndex >= shutterIcons.length()) {
1092 Log.e(TAG, "Invalid mode index: " + modeIndex);
1093 return 0;
1094 }
1095 return shutterIcons.getResourceId(modeIndex, 0);
1096 }
Michael Kolb8872c232013-01-29 10:33:22 -08001097}