blob: 3a51d2901f3742be0fe711b3f48f40dcc4acfbf1 [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;
Doris Liu213a4a02014-02-04 16:57:55 -080044import android.util.FloatMath;
Michael Kolb8872c232013-01-29 10:33:22 -080045import 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;
Angus Kong2bca2102014-03-11 16:27:30 -070057import com.android.camera.debug.Log;
Angus Kongc195e7a2014-02-20 16:56:37 -080058import com.android.camera.filmstrip.ImageData;
Sascha Haeberling37f36112013-08-06 14:31:52 -070059import com.android.camera2.R;
Michael Kolb8872c232013-01-29 10:33:22 -080060
Sascha Haeberling638e6f02013-09-18 14:28:51 -070061import java.io.Closeable;
62import java.io.IOException;
63import java.lang.reflect.Method;
64import java.text.SimpleDateFormat;
65import java.util.Date;
66import java.util.List;
67import java.util.Locale;
68import java.util.StringTokenizer;
69
Michael Kolb8872c232013-01-29 10:33:22 -080070/**
71 * Collection of utility functions used in this package.
72 */
Angus Kongb50b5cb2013-08-09 14:55:20 -070073public class CameraUtil {
Angus Kong2bca2102014-03-11 16:27:30 -070074 private static final Log.Tag TAG = new Log.Tag("Util");
Michael Kolb8872c232013-01-29 10:33:22 -080075
ztenghui16a35202013-09-23 11:35:36 -070076 // For calculate the best fps range for still image capture.
77 private final static int MAX_PREVIEW_FPS_TIMES_1000 = 400000;
78 private final static int PREFERRED_PREVIEW_FPS_TIMES_1000 = 30000;
79
Angus Kongb50b5cb2013-08-09 14:55:20 -070080 // For creating crop intents.
81 public static final String KEY_RETURN_DATA = "return-data";
82 public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked";
83
Michael Kolb8872c232013-01-29 10:33:22 -080084 // Orientation hysteresis amount used in rounding, in degrees
85 public static final int ORIENTATION_HYSTERESIS = 5;
86
87 public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW";
88 // See android.hardware.Camera.ACTION_NEW_PICTURE.
89 public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
90 // See android.hardware.Camera.ACTION_NEW_VIDEO.
91 public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
92
Dan Aminzadefca1c5e2013-08-14 17:36:56 -070093 // Broadcast Action: The camera application has become active in picture-taking mode.
94 public static final String ACTION_CAMERA_STARTED = "com.android.camera.action.CAMERA_STARTED";
95 // Broadcast Action: The camera application is no longer in active picture-taking mode.
96 public static final String ACTION_CAMERA_STOPPED = "com.android.camera.action.CAMERA_STOPPED";
97 // When the camera application is active in picture-taking mode, it listens for this intent,
98 // which upon receipt will trigger the shutter to capture a new picture, as if the user had
99 // pressed the shutter button.
100 public static final String ACTION_CAMERA_SHUTTER_CLICK =
101 "com.android.camera.action.SHUTTER_CLICK";
102
Michael Kolb8872c232013-01-29 10:33:22 -0800103 // Fields from android.hardware.Camera.Parameters
104 public static final String FOCUS_MODE_CONTINUOUS_PICTURE = "continuous-picture";
105 public static final String RECORDING_HINT = "recording-hint";
106 private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
107 private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported";
108 private static final String VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported";
109 public static final String SCENE_MODE_HDR = "hdr";
110 public static final String TRUE = "true";
111 public static final String FALSE = "false";
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700112
113 // Fields for the show-on-maps-functionality
114 private static final String MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
115 private static final String MAPS_CLASS_NAME = "com.google.android.maps.MapsActivity";
116
117 /** Has to be in sync with the receiving MovieActivity. */
118 public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back";
Michael Kolb8872c232013-01-29 10:33:22 -0800119
120 public static boolean isSupported(String value, List<String> supported) {
121 return supported == null ? false : supported.indexOf(value) >= 0;
122 }
123
124 public static boolean isAutoExposureLockSupported(Parameters params) {
125 return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
126 }
127
128 public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
129 return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
130 }
131
132 public static boolean isVideoSnapshotSupported(Parameters params) {
133 return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED));
134 }
135
136 public static boolean isCameraHdrSupported(Parameters params) {
137 List<String> supported = params.getSupportedSceneModes();
138 return (supported != null) && supported.contains(SCENE_MODE_HDR);
139 }
140
Michael Kolb8872c232013-01-29 10:33:22 -0800141 public static boolean isMeteringAreaSupported(Parameters params) {
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700142 return params.getMaxNumMeteringAreas() > 0;
Michael Kolb8872c232013-01-29 10:33:22 -0800143 }
144
Michael Kolb8872c232013-01-29 10:33:22 -0800145 public static boolean isFocusAreaSupported(Parameters params) {
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700146 return (params.getMaxNumFocusAreas() > 0
147 && isSupported(Parameters.FOCUS_MODE_AUTO,
148 params.getSupportedFocusModes()));
Michael Kolb8872c232013-01-29 10:33:22 -0800149 }
150
151 // Private intent extras. Test only.
152 private static final String EXTRAS_CAMERA_FACING =
153 "android.intent.extras.CAMERA_FACING";
154
155 private static float sPixelDensity = 1;
156 private static ImageFileNamer sImageFileNamer;
157
Angus Kongb50b5cb2013-08-09 14:55:20 -0700158 private CameraUtil() {
Michael Kolb8872c232013-01-29 10:33:22 -0800159 }
160
161 public static void initialize(Context context) {
162 DisplayMetrics metrics = new DisplayMetrics();
163 WindowManager wm = (WindowManager)
164 context.getSystemService(Context.WINDOW_SERVICE);
165 wm.getDefaultDisplay().getMetrics(metrics);
166 sPixelDensity = metrics.density;
167 sImageFileNamer = new ImageFileNamer(
168 context.getString(R.string.image_file_name_format));
169 }
170
171 public static int dpToPixel(int dp) {
172 return Math.round(sPixelDensity * dp);
173 }
174
175 // Rotates the bitmap by the specified degree.
176 // If a new bitmap is created, the original bitmap is recycled.
177 public static Bitmap rotate(Bitmap b, int degrees) {
178 return rotateAndMirror(b, degrees, false);
179 }
180
181 // Rotates and/or mirrors the bitmap. If a new bitmap is created, the
182 // original bitmap is recycled.
183 public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) {
184 if ((degrees != 0 || mirror) && b != null) {
185 Matrix m = new Matrix();
186 // Mirror first.
187 // horizontal flip + rotation = -rotation + horizontal flip
188 if (mirror) {
189 m.postScale(-1, 1);
190 degrees = (degrees + 360) % 360;
191 if (degrees == 0 || degrees == 180) {
192 m.postTranslate(b.getWidth(), 0);
193 } else if (degrees == 90 || degrees == 270) {
194 m.postTranslate(b.getHeight(), 0);
195 } else {
196 throw new IllegalArgumentException("Invalid degrees=" + degrees);
197 }
198 }
199 if (degrees != 0) {
200 // clockwise
201 m.postRotate(degrees,
202 (float) b.getWidth() / 2, (float) b.getHeight() / 2);
203 }
204
205 try {
206 Bitmap b2 = Bitmap.createBitmap(
207 b, 0, 0, b.getWidth(), b.getHeight(), m, true);
208 if (b != b2) {
209 b.recycle();
210 b = b2;
211 }
212 } catch (OutOfMemoryError ex) {
213 // We have no memory to rotate. Return the original bitmap.
214 }
215 }
216 return b;
217 }
218
219 /*
220 * Compute the sample size as a function of minSideLength
221 * and maxNumOfPixels.
222 * minSideLength is used to specify that minimal width or height of a
223 * bitmap.
224 * maxNumOfPixels is used to specify the maximal size in pixels that is
225 * tolerable in terms of memory usage.
226 *
227 * The function returns a sample size based on the constraints.
228 * Both size and minSideLength can be passed in as -1
229 * which indicates no care of the corresponding constraint.
230 * The functions prefers returning a sample size that
231 * generates a smaller bitmap, unless minSideLength = -1.
232 *
233 * Also, the function rounds up the sample size to a power of 2 or multiple
234 * of 8 because BitmapFactory only honors sample size this way.
235 * For example, BitmapFactory downsamples an image by 2 even though the
236 * request is 3. So we round up the sample size to avoid OOM.
237 */
238 public static int computeSampleSize(BitmapFactory.Options options,
239 int minSideLength, int maxNumOfPixels) {
240 int initialSize = computeInitialSampleSize(options, minSideLength,
241 maxNumOfPixels);
242
243 int roundedSize;
244 if (initialSize <= 8) {
245 roundedSize = 1;
246 while (roundedSize < initialSize) {
247 roundedSize <<= 1;
248 }
249 } else {
250 roundedSize = (initialSize + 7) / 8 * 8;
251 }
252
253 return roundedSize;
254 }
255
256 private static int computeInitialSampleSize(BitmapFactory.Options options,
257 int minSideLength, int maxNumOfPixels) {
258 double w = options.outWidth;
259 double h = options.outHeight;
260
261 int lowerBound = (maxNumOfPixels < 0) ? 1 :
262 (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
263 int upperBound = (minSideLength < 0) ? 128 :
264 (int) Math.min(Math.floor(w / minSideLength),
265 Math.floor(h / minSideLength));
266
267 if (upperBound < lowerBound) {
268 // return the larger one when there is no overlapping zone.
269 return lowerBound;
270 }
271
272 if (maxNumOfPixels < 0 && minSideLength < 0) {
273 return 1;
274 } else if (minSideLength < 0) {
275 return lowerBound;
276 } else {
277 return upperBound;
278 }
279 }
280
281 public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) {
282 try {
283 BitmapFactory.Options options = new BitmapFactory.Options();
284 options.inJustDecodeBounds = true;
285 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
286 options);
287 if (options.mCancel || options.outWidth == -1
288 || options.outHeight == -1) {
289 return null;
290 }
291 options.inSampleSize = computeSampleSize(
292 options, -1, maxNumOfPixels);
293 options.inJustDecodeBounds = false;
294
295 options.inDither = false;
296 options.inPreferredConfig = Bitmap.Config.ARGB_8888;
297 return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
298 options);
299 } catch (OutOfMemoryError ex) {
300 Log.e(TAG, "Got oom exception ", ex);
301 return null;
302 }
303 }
304
305 public static void closeSilently(Closeable c) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100306 if (c == null) {
307 return;
308 }
Michael Kolb8872c232013-01-29 10:33:22 -0800309 try {
310 c.close();
311 } catch (Throwable t) {
312 // do nothing
313 }
314 }
315
316 public static void Assert(boolean cond) {
317 if (!cond) {
318 throw new AssertionError();
319 }
320 }
321
Michael Kolb8872c232013-01-29 10:33:22 -0800322 private static void throwIfCameraDisabled(Activity activity) throws CameraDisabledException {
323 // Check if device policy has disabled the camera.
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700324 DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService(
325 Context.DEVICE_POLICY_SERVICE);
326 if (dpm.getCameraDisabled(null)) {
327 throw new CameraDisabledException();
Michael Kolb8872c232013-01-29 10:33:22 -0800328 }
329 }
330
Michael Kolb8872c232013-01-29 10:33:22 -0800331 public static void showErrorAndFinish(final Activity activity, int msgId) {
332 DialogInterface.OnClickListener buttonListener =
333 new DialogInterface.OnClickListener() {
334 @Override
335 public void onClick(DialogInterface dialog, int which) {
336 activity.finish();
337 }
338 };
339 TypedValue out = new TypedValue();
340 activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true);
341 new AlertDialog.Builder(activity)
342 .setCancelable(false)
343 .setTitle(R.string.camera_error_title)
344 .setMessage(msgId)
345 .setNeutralButton(R.string.dialog_ok, buttonListener)
346 .setIcon(out.resourceId)
347 .show();
348 }
349
350 public static <T> T checkNotNull(T object) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100351 if (object == null) {
352 throw new NullPointerException();
353 }
Michael Kolb8872c232013-01-29 10:33:22 -0800354 return object;
355 }
356
357 public static boolean equals(Object a, Object b) {
358 return (a == b) || (a == null ? false : a.equals(b));
359 }
360
361 public static int nextPowerOf2(int n) {
362 n -= 1;
363 n |= n >>> 16;
364 n |= n >>> 8;
365 n |= n >>> 4;
366 n |= n >>> 2;
367 n |= n >>> 1;
368 return n + 1;
369 }
370
371 public static float distance(float x, float y, float sx, float sy) {
372 float dx = x - sx;
373 float dy = y - sy;
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700374 return (float) Math.sqrt(dx * dx + dy * dy);
Michael Kolb8872c232013-01-29 10:33:22 -0800375 }
376
377 public static int clamp(int x, int min, int max) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100378 if (x > max) {
379 return max;
380 }
381 if (x < min) {
382 return min;
383 }
Michael Kolb8872c232013-01-29 10:33:22 -0800384 return x;
385 }
386
Igor Murashkin160b0362013-10-14 14:57:30 -0700387 public static float clamp(float x, float min, float max) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100388 if (x > max) {
389 return max;
390 }
391 if (x < min) {
392 return min;
393 }
Igor Murashkin160b0362013-10-14 14:57:30 -0700394 return x;
395 }
396
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100397 public static int getDisplayRotation(Context context) {
398 WindowManager windowManager = (WindowManager) context
399 .getSystemService(Context.WINDOW_SERVICE);
400 int rotation = windowManager.getDefaultDisplay()
Michael Kolb8872c232013-01-29 10:33:22 -0800401 .getRotation();
402 switch (rotation) {
403 case Surface.ROTATION_0: return 0;
404 case Surface.ROTATION_90: return 90;
405 case Surface.ROTATION_180: return 180;
406 case Surface.ROTATION_270: return 270;
407 }
408 return 0;
409 }
410
Doris Liu0ba8eaa2013-10-16 12:51:02 -0700411 /**
412 * Calculate the default orientation of the device based on the width and
413 * height of the display when rotation = 0 (i.e. natural width and height)
414 * @param activity the activity context
415 * @return whether the default orientation of the device is portrait
416 */
417 public static boolean isDefaultToPortrait(Activity activity) {
418 Display currentDisplay = activity.getWindowManager().getDefaultDisplay();
419 Point displaySize = new Point();
420 currentDisplay.getSize(displaySize);
421 int orientation = currentDisplay.getRotation();
422 int naturalWidth, naturalHeight;
423 if (orientation == Surface.ROTATION_0 || orientation == Surface.ROTATION_180) {
424 naturalWidth = displaySize.x;
425 naturalHeight = displaySize.y;
426 } else {
427 naturalWidth = displaySize.y;
428 naturalHeight = displaySize.x;
429 }
430 return naturalWidth < naturalHeight;
431 }
432
Michael Kolb8872c232013-01-29 10:33:22 -0800433 public static int getDisplayOrientation(int degrees, int cameraId) {
434 // See android.hardware.Camera.setDisplayOrientation for
435 // documentation.
436 Camera.CameraInfo info = new Camera.CameraInfo();
437 Camera.getCameraInfo(cameraId, info);
438 int result;
439 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
440 result = (info.orientation + degrees) % 360;
441 result = (360 - result) % 360; // compensate the mirror
442 } else { // back-facing
443 result = (info.orientation - degrees + 360) % 360;
444 }
445 return result;
446 }
447
448 public static int getCameraOrientation(int cameraId) {
449 Camera.CameraInfo info = new Camera.CameraInfo();
450 Camera.getCameraInfo(cameraId, info);
451 return info.orientation;
452 }
453
454 public static int roundOrientation(int orientation, int orientationHistory) {
455 boolean changeOrientation = false;
456 if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
457 changeOrientation = true;
458 } else {
459 int dist = Math.abs(orientation - orientationHistory);
460 dist = Math.min( dist, 360 - dist );
461 changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS );
462 }
463 if (changeOrientation) {
464 return ((orientation + 45) / 90 * 90) % 360;
465 }
466 return orientationHistory;
467 }
468
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100469 private static Point getDefaultDisplaySize(Context context, Point size) {
470 WindowManager windowManager = (WindowManager) context
471 .getSystemService(Context.WINDOW_SERVICE);
472 windowManager.getDefaultDisplay().getSize(size);
Michael Kolb8872c232013-01-29 10:33:22 -0800473 return size;
474 }
475
Angus Kong5f8c30e2014-03-06 17:15:08 -0800476 public static Size getOptimalPreviewSize(Context context,
Michael Kolb8872c232013-01-29 10:33:22 -0800477 List<Size> sizes, double targetRatio) {
Michael Kolb8872c232013-01-29 10:33:22 -0800478
Ruben Brunk714d4d02013-10-09 16:01:16 -0700479 Point[] points = new Point[sizes.size()];
480
481 int index = 0;
482 for (Size s : sizes) {
483 points[index++] = new Point(s.width, s.height);
484 }
485
Angus Kong5f8c30e2014-03-06 17:15:08 -0800486 int optimalPickIndex = getOptimalPreviewSize(context, points, targetRatio);
Ruben Brunk714d4d02013-10-09 16:01:16 -0700487 return (optimalPickIndex == -1) ? null : sizes.get(optimalPickIndex);
488 }
489
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100490 public static int getOptimalPreviewSize(Context context,
Ruben Brunk714d4d02013-10-09 16:01:16 -0700491 Point[] sizes, double targetRatio) {
492 // Use a very small tolerance because we want an exact match.
493 final double ASPECT_TOLERANCE = 0.01;
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100494 if (sizes == null) {
495 return -1;
496 }
Ruben Brunk714d4d02013-10-09 16:01:16 -0700497
498 int optimalSizeIndex = -1;
Michael Kolb8872c232013-01-29 10:33:22 -0800499 double minDiff = Double.MAX_VALUE;
500
501 // Because of bugs of overlay and layout, we sometimes will try to
502 // layout the viewfinder in the portrait orientation and thus get the
503 // wrong size of preview surface. When we change the preview size, the
504 // new overlay will be created before the old one closed, which causes
505 // an exception. For now, just get the screen size.
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100506 Point point = getDefaultDisplaySize(context, new Point());
Michael Kolb8872c232013-01-29 10:33:22 -0800507 int targetHeight = Math.min(point.x, point.y);
508 // Try to find an size match aspect ratio and size
Ruben Brunk714d4d02013-10-09 16:01:16 -0700509 for (int i = 0; i < sizes.length; i++) {
510 Point size = sizes[i];
511 double ratio = (double) size.x / size.y;
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100512 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) {
513 continue;
514 }
Ruben Brunk714d4d02013-10-09 16:01:16 -0700515 if (Math.abs(size.y - targetHeight) < minDiff) {
516 optimalSizeIndex = i;
517 minDiff = Math.abs(size.y - targetHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800518 }
519 }
520 // Cannot find the one match the aspect ratio. This should not happen.
521 // Ignore the requirement.
Ruben Brunk714d4d02013-10-09 16:01:16 -0700522 if (optimalSizeIndex == -1) {
Michael Kolb8872c232013-01-29 10:33:22 -0800523 Log.w(TAG, "No preview size match the aspect ratio");
524 minDiff = Double.MAX_VALUE;
Ruben Brunk714d4d02013-10-09 16:01:16 -0700525 for (int i = 0; i < sizes.length; i++) {
526 Point size = sizes[i];
527 if (Math.abs(size.y - targetHeight) < minDiff) {
528 optimalSizeIndex = i;
529 minDiff = Math.abs(size.y - targetHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800530 }
531 }
532 }
Ruben Brunk714d4d02013-10-09 16:01:16 -0700533 return optimalSizeIndex;
Michael Kolb8872c232013-01-29 10:33:22 -0800534 }
535
536 // Returns the largest picture size which matches the given aspect ratio.
537 public static Size getOptimalVideoSnapshotPictureSize(
538 List<Size> sizes, double targetRatio) {
539 // Use a very small tolerance because we want an exact match.
540 final double ASPECT_TOLERANCE = 0.001;
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100541 if (sizes == null) {
542 return null;
543 }
Michael Kolb8872c232013-01-29 10:33:22 -0800544
545 Size optimalSize = null;
546
547 // Try to find a size matches aspect ratio and has the largest width
548 for (Size size : sizes) {
549 double ratio = (double) size.width / size.height;
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100550 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) {
551 continue;
552 }
Michael Kolb8872c232013-01-29 10:33:22 -0800553 if (optimalSize == null || size.width > optimalSize.width) {
554 optimalSize = size;
555 }
556 }
557
558 // Cannot find one that matches the aspect ratio. This should not happen.
559 // Ignore the requirement.
560 if (optimalSize == null) {
561 Log.w(TAG, "No picture size match the aspect ratio");
562 for (Size size : sizes) {
563 if (optimalSize == null || size.width > optimalSize.width) {
564 optimalSize = size;
565 }
566 }
567 }
568 return optimalSize;
569 }
570
571 public static void dumpParameters(Parameters parameters) {
572 String flattened = parameters.flatten();
573 StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
574 Log.d(TAG, "Dump all camera parameters:");
575 while (tokenizer.hasMoreElements()) {
576 Log.d(TAG, tokenizer.nextToken());
577 }
578 }
579
580 /**
581 * Returns whether the device is voice-capable (meaning, it can do MMS).
582 */
583 public static boolean isMmsCapable(Context context) {
584 TelephonyManager telephonyManager = (TelephonyManager)
585 context.getSystemService(Context.TELEPHONY_SERVICE);
586 if (telephonyManager == null) {
587 return false;
588 }
589
590 try {
591 Class<?> partypes[] = new Class[0];
592 Method sIsVoiceCapable = TelephonyManager.class.getMethod(
593 "isVoiceCapable", partypes);
594
595 Object arglist[] = new Object[0];
596 Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist);
597 return (Boolean) retobj;
598 } catch (java.lang.reflect.InvocationTargetException ite) {
599 // Failure, must be another device.
600 // Assume that it is voice capable.
601 } catch (IllegalAccessException iae) {
602 // Failure, must be an other device.
603 // Assume that it is voice capable.
604 } catch (NoSuchMethodException nsme) {
605 }
606 return true;
607 }
608
609 // This is for test only. Allow the camera to launch the specific camera.
610 public static int getCameraFacingIntentExtras(Activity currentActivity) {
611 int cameraId = -1;
612
613 int intentCameraId =
Angus Kongb50b5cb2013-08-09 14:55:20 -0700614 currentActivity.getIntent().getIntExtra(CameraUtil.EXTRAS_CAMERA_FACING, -1);
Michael Kolb8872c232013-01-29 10:33:22 -0800615
616 if (isFrontCameraIntent(intentCameraId)) {
617 // Check if the front camera exist
Angus Kong20fad242013-11-11 18:23:46 -0800618 int frontCameraId = ((CameraActivity) currentActivity).getCameraProvider()
619 .getFirstFrontCameraId();
Michael Kolb8872c232013-01-29 10:33:22 -0800620 if (frontCameraId != -1) {
621 cameraId = frontCameraId;
622 }
623 } else if (isBackCameraIntent(intentCameraId)) {
624 // Check if the back camera exist
Angus Kong20fad242013-11-11 18:23:46 -0800625 int backCameraId = ((CameraActivity) currentActivity).getCameraProvider()
626 .getFirstBackCameraId();
Michael Kolb8872c232013-01-29 10:33:22 -0800627 if (backCameraId != -1) {
628 cameraId = backCameraId;
629 }
630 }
631 return cameraId;
632 }
633
634 private static boolean isFrontCameraIntent(int intentCameraId) {
635 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
636 }
637
638 private static boolean isBackCameraIntent(int intentCameraId) {
639 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
640 }
641
642 private static int sLocation[] = new int[2];
643
644 // This method is not thread-safe.
645 public static boolean pointInView(float x, float y, View v) {
646 v.getLocationInWindow(sLocation);
647 return x >= sLocation[0] && x < (sLocation[0] + v.getWidth())
648 && y >= sLocation[1] && y < (sLocation[1] + v.getHeight());
649 }
650
651 public static int[] getRelativeLocation(View reference, View view) {
652 reference.getLocationInWindow(sLocation);
653 int referenceX = sLocation[0];
654 int referenceY = sLocation[1];
655 view.getLocationInWindow(sLocation);
656 sLocation[0] -= referenceX;
657 sLocation[1] -= referenceY;
658 return sLocation;
659 }
660
661 public static boolean isUriValid(Uri uri, ContentResolver resolver) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100662 if (uri == null) {
663 return false;
664 }
Michael Kolb8872c232013-01-29 10:33:22 -0800665
666 try {
667 ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
668 if (pfd == null) {
669 Log.e(TAG, "Fail to open URI. URI=" + uri);
670 return false;
671 }
672 pfd.close();
673 } catch (IOException ex) {
674 return false;
675 }
676 return true;
677 }
678
Michael Kolb8872c232013-01-29 10:33:22 -0800679 public static void dumpRect(RectF rect, String msg) {
680 Log.v(TAG, msg + "=(" + rect.left + "," + rect.top
681 + "," + rect.right + "," + rect.bottom + ")");
682 }
683
684 public static void rectFToRect(RectF rectF, Rect rect) {
685 rect.left = Math.round(rectF.left);
686 rect.top = Math.round(rectF.top);
687 rect.right = Math.round(rectF.right);
688 rect.bottom = Math.round(rectF.bottom);
689 }
690
Doris Liu36ebcb12013-10-28 14:44:24 -0700691 public static Rect rectFToRect(RectF rectF) {
692 Rect rect = new Rect();
693 rectFToRect(rectF, rect);
694 return rect;
695 }
696
697 public static RectF rectToRectF(Rect r) {
698 return new RectF(r.left, r.top, r.right, r.bottom);
699 }
700
Michael Kolb8872c232013-01-29 10:33:22 -0800701 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
702 int viewWidth, int viewHeight) {
703 // Need mirror for front camera.
704 matrix.setScale(mirror ? -1 : 1, 1);
705 // This is the value for android.hardware.Camera.setDisplayOrientation.
706 matrix.postRotate(displayOrientation);
707 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
708 // UI coordinates range from (0, 0) to (width, height).
709 matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
710 matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
711 }
712
Doris Liu36ebcb12013-10-28 14:44:24 -0700713 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
714 Rect previewRect) {
715 // Need mirror for front camera.
716 matrix.setScale(mirror ? -1 : 1, 1);
717 // This is the value for android.hardware.Camera.setDisplayOrientation.
718 matrix.postRotate(displayOrientation);
719
720 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
721 // We need to map camera driver coordinates to preview rect coordinates
722 Matrix mapping = new Matrix();
723 mapping.setRectToRect(new RectF(-1000, -1000, 1000, 1000), rectToRectF(previewRect),
724 Matrix.ScaleToFit.FILL);
725 matrix.setConcat(mapping, matrix);
726 }
727
Michael Kolb8872c232013-01-29 10:33:22 -0800728 public static String createJpegName(long dateTaken) {
729 synchronized (sImageFileNamer) {
730 return sImageFileNamer.generateName(dateTaken);
731 }
732 }
733
734 public static void broadcastNewPicture(Context context, Uri uri) {
735 context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri));
736 // Keep compatibility
737 context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri));
738 }
739
740 public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100741 if (view.getVisibility() == View.VISIBLE) {
742 return;
743 }
Michael Kolb8872c232013-01-29 10:33:22 -0800744
745 view.setVisibility(View.VISIBLE);
746 Animation animation = new AlphaAnimation(startAlpha, endAlpha);
747 animation.setDuration(duration);
748 view.startAnimation(animation);
749 }
750
751 public static void fadeIn(View view) {
752 fadeIn(view, 0F, 1F, 400);
753
754 // We disabled the button in fadeOut(), so enable it here.
755 view.setEnabled(true);
756 }
757
758 public static void fadeOut(View view) {
Sascha Haeberling846d3ab2014-02-04 12:48:55 +0100759 if (view.getVisibility() != View.VISIBLE) {
760 return;
761 }
Michael Kolb8872c232013-01-29 10:33:22 -0800762
763 // Since the button is still clickable before fade-out animation
764 // ends, we disable the button first to block click.
765 view.setEnabled(false);
766 Animation animation = new AlphaAnimation(1F, 0F);
767 animation.setDuration(400);
768 view.startAnimation(animation);
769 view.setVisibility(View.GONE);
770 }
771
Sascha Haeberlinga7cbfc02014-02-14 11:06:03 +0100772 public static int getJpegRotation(CameraInfo info, int orientation) {
773 // See android.hardware.Camera.Parameters.setRotation for
774 // documentation.
775 int rotation = 0;
776 if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
777 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
778 rotation = (info.orientation - orientation + 360) % 360;
779 } else { // back-facing camera
780 rotation = (info.orientation + orientation) % 360;
781 }
782 }
783 return rotation;
784 }
Michael Kolb8872c232013-01-29 10:33:22 -0800785
Sascha Haeberling37f36112013-08-06 14:31:52 -0700786 /**
787 * Down-samples a jpeg byte array.
788 * @param data a byte array of jpeg data
789 * @param downSampleFactor down-sample factor
790 * @return decoded and down-sampled bitmap
791 */
792 public static Bitmap downSample(final byte[] data, int downSampleFactor) {
793 final BitmapFactory.Options opts = new BitmapFactory.Options();
794 // Downsample the image
795 opts.inSampleSize = downSampleFactor;
796 return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
797 }
798
Michael Kolb8872c232013-01-29 10:33:22 -0800799 public static void setGpsParameters(Parameters parameters, Location loc) {
800 // Clear previous GPS location from the parameters.
801 parameters.removeGpsData();
802
803 // We always encode GpsTimeStamp
804 parameters.setGpsTimestamp(System.currentTimeMillis() / 1000);
805
806 // Set GPS location.
807 if (loc != null) {
808 double lat = loc.getLatitude();
809 double lon = loc.getLongitude();
810 boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
811
812 if (hasLatLon) {
813 Log.d(TAG, "Set gps location");
814 parameters.setGpsLatitude(lat);
815 parameters.setGpsLongitude(lon);
816 parameters.setGpsProcessingMethod(loc.getProvider().toUpperCase());
817 if (loc.hasAltitude()) {
818 parameters.setGpsAltitude(loc.getAltitude());
819 } else {
820 // for NETWORK_PROVIDER location provider, we may have
821 // no altitude information, but the driver needs it, so
822 // we fake one.
823 parameters.setGpsAltitude(0);
824 }
825 if (loc.getTime() != 0) {
826 // Location.getTime() is UTC in milliseconds.
827 // gps-timestamp is UTC in seconds.
828 long utcTimeSeconds = loc.getTime() / 1000;
829 parameters.setGpsTimestamp(utcTimeSeconds);
830 }
831 } else {
832 loc = null;
833 }
834 }
835 }
836
ztenghui16a35202013-09-23 11:35:36 -0700837 /**
838 * For still image capture, we need to get the right fps range such that the
839 * camera can slow down the framerate to allow for less-noisy/dark
840 * viewfinder output in dark conditions.
841 *
842 * @param params Camera's parameters.
843 * @return null if no appropiate fps range can't be found. Otherwise, return
844 * the right range.
845 */
846 public static int[] getPhotoPreviewFpsRange(Parameters params) {
Ruben Brunk2a74dfd2013-10-29 18:28:32 -0700847 return getPhotoPreviewFpsRange(params.getSupportedPreviewFpsRange());
848 }
849
850 public static int[] getPhotoPreviewFpsRange(List<int[]> frameRates) {
ztenghui16a35202013-09-23 11:35:36 -0700851 if (frameRates.size() == 0) {
852 Log.e(TAG, "No suppoted frame rates returned!");
853 return null;
854 }
855
856 // Find the lowest min rate in supported ranges who can cover 30fps.
857 int lowestMinRate = MAX_PREVIEW_FPS_TIMES_1000;
858 for (int[] rate : frameRates) {
859 int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX];
860 int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX];
861 if (maxFps >= PREFERRED_PREVIEW_FPS_TIMES_1000 &&
862 minFps <= PREFERRED_PREVIEW_FPS_TIMES_1000 &&
863 minFps < lowestMinRate) {
864 lowestMinRate = minFps;
865 }
866 }
867
868 // Find all the modes with the lowest min rate found above, the pick the
869 // one with highest max rate.
870 int resultIndex = -1;
871 int highestMaxRate = 0;
872 for (int i = 0; i < frameRates.size(); i++) {
873 int[] rate = frameRates.get(i);
874 int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX];
875 int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX];
876 if (minFps == lowestMinRate && highestMaxRate < maxFps) {
877 highestMaxRate = maxFps;
878 resultIndex = i;
879 }
880 }
881
882 if (resultIndex >= 0) {
883 return frameRates.get(resultIndex);
884 }
885 Log.e(TAG, "Can't find an appropiate frame rate range!");
886 return null;
887 }
Angus Kongd82eae22013-06-11 12:00:15 -0700888
889 public static int[] getMaxPreviewFpsRange(Parameters params) {
890 List<int[]> frameRates = params.getSupportedPreviewFpsRange();
891 if (frameRates != null && frameRates.size() > 0) {
892 // The list is sorted. Return the last element.
893 return frameRates.get(frameRates.size() - 1);
894 }
895 return new int[0];
896 }
897
Angus Kong20fad242013-11-11 18:23:46 -0800898 public static void throwIfCameraDisabled(Context context) throws CameraDisabledException {
899 // Check if device policy has disabled the camera.
900 DevicePolicyManager dpm =
901 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
902 if (dpm.getCameraDisabled(null)) {
903 throw new CameraDisabledException();
904 }
905 }
906
Doris Liu213a4a02014-02-04 16:57:55 -0800907 /**
908 * Generates a 1d Gaussian mask of the input array size, and store the mask
909 * in the input array.
910 *
911 * @param mask empty array of size n, where n will be used as the size of the
912 * Gaussian mask, and the array will be populated with the values
913 * of the mask.
914 */
915 private static void getGaussianMask(float[] mask) {
916 int len = mask.length;
917 int mid = len / 2;
918 float sigma = len;
919 float sum = 0;
920 for (int i = 0; i <= mid; i++) {
921 float ex = FloatMath.exp(-(i - mid) * (i - mid) / (mid * mid))
922 / (2 * sigma * sigma);
923 int symmetricIndex = len - 1 - i;
924 mask[i] = ex;
925 mask[symmetricIndex] = ex;
926 sum += mask[i];
927 if (i != symmetricIndex) {
928 sum += mask[symmetricIndex];
929 }
930 }
931
932 for (int i = 0; i < mask.length; i++) {
933 mask[i] /= sum;
934 }
935
936 }
937
938 /**
939 * Add two pixels together where the second pixel will be applied with a weight.
940 *
941 * @param pixel pixel color value of weight 1
942 * @param newPixel second pixel color value where the weight will be applied
943 * @param weight a float weight that will be applied to the second pixel color
944 * @return the weighted addition of the two pixels
945 */
946 public static int addPixel(int pixel, int newPixel, float weight) {
947 // TODO: scale weight to [0, 1024] to avoid casting to float and back to int.
Sascha Haeberlinga7cbfc02014-02-14 11:06:03 +0100948 int r = ((pixel & 0x00ff0000) + (int) ((newPixel & 0x00ff0000) * weight)) & 0x00ff0000;
949 int g = ((pixel & 0x0000ff00) + (int) ((newPixel & 0x0000ff00) * weight)) & 0x0000ff00;
950 int b = ((pixel & 0x000000ff) + (int) ((newPixel & 0x000000ff) * weight)) & 0x000000ff;
Doris Liu213a4a02014-02-04 16:57:55 -0800951 return 0xff000000 | r | g | b;
952 }
953
954 /**
955 * Apply blur to the input image represented in an array of colors and put the
956 * output image, in the form of an array of colors, into the output array.
957 *
958 * @param src source array of colors
959 * @param out output array of colors after the blur
960 * @param w width of the image
961 * @param h height of the image
962 * @param size size of the Gaussian blur mask
963 */
964 public static void blur(int[] src, int[] out, int w, int h, int size) {
965 float[] k = new float[size];
966 int off = size / 2;
967
968 getGaussianMask(k);
969
970 int[] tmp = new int[src.length];
971
972 // Apply the 1d Gaussian mask horizontally to the image and put the intermediate
973 // results in a temporary array.
974 int rowPointer = 0;
975 for (int y = 0; y < h; y++) {
976 for (int x = 0; x < w; x++) {
977 int sum = 0;
978 for (int i = 0; i < k.length; i++) {
979 int dx = x + i - off;
980 dx = clamp(dx, 0, w - 1);
981 sum = addPixel(sum, src[rowPointer + dx], k[i]);
982 }
983 tmp[x + rowPointer] = sum;
984 }
985 rowPointer += w;
986 }
987
988 // Apply the 1d Gaussian mask vertically to the intermediate array, and
989 // the final results will be stored in the output array.
990 for (int x = 0; x < w; x++) {
991 rowPointer = 0;
992 for (int y = 0; y < h; y++) {
993 int sum = 0;
994 for (int i = 0; i < k.length; i++) {
995 int dy = y + i - off;
996 dy = clamp(dy, 0, h - 1);
997 sum = addPixel(sum, tmp[dy * w + x], k[i]);
998 }
999 out[x + rowPointer] = sum;
1000 rowPointer += w;
1001 }
1002 }
1003 }
1004
Angus Kongc195e7a2014-02-20 16:56:37 -08001005 /**
1006 * Calculates a new dimension to fill the bound with the original aspect
1007 * ratio preserved.
1008 *
1009 * @param imageWidth The original width.
1010 * @param imageHeight The original height.
1011 * @param imageRotation The clockwise rotation in degrees of the image
1012 * which the original dimension comes from.
1013 * @param boundWidth The width of the bound.
1014 * @param boundHeight The height of the bound.
1015 *
1016 * @returns The final width/height stored in Point.x/Point.y to fill the
1017 * bounds and preserve image aspect ratio.
1018 */
1019 public static Point resizeToFill(int imageWidth, int imageHeight, int imageRotation,
1020 int boundWidth, int boundHeight) {
1021 if (imageRotation % 180 != 0) {
1022 // Swap width and height.
1023 int savedWidth = imageWidth;
1024 imageWidth = imageHeight;
1025 imageHeight = savedWidth;
1026 }
1027 if (imageWidth == ImageData.SIZE_FULL
1028 || imageHeight == ImageData.SIZE_FULL) {
1029 imageWidth = boundWidth;
1030 imageHeight = boundHeight;
1031 }
1032
1033 Point p = new Point();
1034 p.x = boundWidth;
1035 p.y = boundHeight;
1036
1037 if (imageWidth * boundHeight > boundWidth * imageHeight) {
1038 p.y = imageHeight * p.x / imageWidth;
1039 } else {
1040 p.x = imageWidth * p.y / imageHeight;
1041 }
1042
1043 return p;
1044 }
1045
Michael Kolb8872c232013-01-29 10:33:22 -08001046 private static class ImageFileNamer {
Igor Murashkin160b0362013-10-14 14:57:30 -07001047 private final SimpleDateFormat mFormat;
Michael Kolb8872c232013-01-29 10:33:22 -08001048
1049 // The date (in milliseconds) used to generate the last name.
1050 private long mLastDate;
1051
1052 // Number of names generated for the same second.
1053 private int mSameSecondCount;
1054
1055 public ImageFileNamer(String format) {
1056 mFormat = new SimpleDateFormat(format);
1057 }
1058
1059 public String generateName(long dateTaken) {
1060 Date date = new Date(dateTaken);
1061 String result = mFormat.format(date);
1062
1063 // If the last name was generated for the same second,
1064 // we append _1, _2, etc to the name.
1065 if (dateTaken / 1000 == mLastDate / 1000) {
1066 mSameSecondCount++;
1067 result += "_" + mSameSecondCount;
1068 } else {
1069 mLastDate = dateTaken;
1070 mSameSecondCount = 0;
1071 }
1072
1073 return result;
1074 }
1075 }
Angus Kongb40738a2013-06-03 14:55:34 -07001076
Sascha Haeberlingb7639c62013-09-09 14:42:43 -07001077 public static void playVideo(Activity activity, Uri uri, String title) {
Angus Kongb40738a2013-06-03 14:55:34 -07001078 try {
ztenghui98b2a282013-10-11 17:15:40 -07001079 boolean isSecureCamera = ((CameraActivity)activity).isSecureCamera();
1080 if (!isSecureCamera) {
Mangesh Ghiware2d939fe2013-10-14 21:19:22 -07001081 Intent intent = IntentHelper.getVideoPlayerIntent(activity, uri)
ztenghui98b2a282013-10-11 17:15:40 -07001082 .putExtra(Intent.EXTRA_TITLE, title)
1083 .putExtra(KEY_TREAT_UP_AS_BACK, true);
1084 activity.startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
1085 } else {
1086 // In order not to send out any intent to be intercepted and
1087 // show the lock screen immediately, we just let the secure
1088 // camera activity finish.
1089 activity.finish();
1090 }
Angus Kongb40738a2013-06-03 14:55:34 -07001091 } catch (ActivityNotFoundException e) {
Sascha Haeberlingb7639c62013-09-09 14:42:43 -07001092 Toast.makeText(activity, activity.getString(R.string.video_err),
Angus Kongb40738a2013-06-03 14:55:34 -07001093 Toast.LENGTH_SHORT).show();
1094 }
1095 }
Sascha Haeberlingfae11a12013-08-15 14:29:49 -07001096
1097 /**
1098 * Starts GMM with the given location shown. If this fails, and GMM could
1099 * not be found, we use a geo intent as a fallback.
1100 *
Sascha Haeberlingb7639c62013-09-09 14:42:43 -07001101 * @param activity the activity to use for launching the Maps intent.
Sascha Haeberlingfae11a12013-08-15 14:29:49 -07001102 * @param latLong a 2-element array containing {latitude/longitude}.
1103 */
Sascha Haeberlingb7639c62013-09-09 14:42:43 -07001104 public static void showOnMap(Activity activity, double[] latLong) {
Sascha Haeberlingfae11a12013-08-15 14:29:49 -07001105 try {
1106 // We don't use "geo:latitude,longitude" because it only centers
1107 // the MapView to the specified location, but we need a marker
1108 // for further operations (routing to/from).
1109 // The q=(lat, lng) syntax is suggested by geo-team.
1110 String uri = String.format(Locale.ENGLISH, "http://maps.google.com/maps?f=q&q=(%f,%f)",
1111 latLong[0], latLong[1]);
1112 ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME,
1113 MAPS_CLASS_NAME);
1114 Intent mapsIntent = new Intent(Intent.ACTION_VIEW,
1115 Uri.parse(uri)).setComponent(compName);
Sascha Haeberlingb7639c62013-09-09 14:42:43 -07001116 activity.startActivityForResult(mapsIntent,
1117 CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
Sascha Haeberlingfae11a12013-08-15 14:29:49 -07001118 } catch (ActivityNotFoundException e) {
1119 // Use the "geo intent" if no GMM is installed
1120 Log.e(TAG, "GMM activity not found!", e);
1121 String url = String.format(Locale.ENGLISH, "geo:%f,%f", latLong[0], latLong[1]);
1122 Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
Sascha Haeberlingb7639c62013-09-09 14:42:43 -07001123 activity.startActivity(mapsIntent);
Sascha Haeberlingfae11a12013-08-15 14:29:49 -07001124 }
1125 }
Angus Kong7d2388d2013-09-18 23:56:01 -07001126
Angus Kongb21215a2013-09-20 18:41:46 -07001127 /**
1128 * Dumps the stack trace.
1129 *
1130 * @param level How many levels of the stack are dumped. 0 means all.
1131 * @return A {@link java.lang.String} of all the output with newline
1132 * between each.
1133 */
Angus Kong7d2388d2013-09-18 23:56:01 -07001134 public static String dumpStackTrace(int level) {
1135 StackTraceElement[] elems = Thread.currentThread().getStackTrace();
1136 // Ignore the first 3 elements.
Angus Kongb21215a2013-09-20 18:41:46 -07001137 level = (level == 0 ? elems.length : Math.min(level + 3, elems.length));
Angus Kong7d2388d2013-09-18 23:56:01 -07001138 String ret = new String();
1139 for (int i = 3; i < level; i++) {
Angus Kongb21215a2013-09-20 18:41:46 -07001140 ret = ret + "\t" + elems[i].toString() + '\n';
Angus Kong7d2388d2013-09-18 23:56:01 -07001141 }
1142 return ret;
1143 }
Doris Liubd1b8f92014-01-03 17:59:51 -08001144
1145
1146 /**
1147 * Gets the theme color of a specific mode.
1148 *
1149 * @param modeIndex index of the mode
1150 * @param context current context
1151 * @return theme color of the mode if input index is valid, otherwise 0
1152 */
1153 public static int getCameraThemeColorId(int modeIndex, Context context) {
1154
1155 // Find the theme color using id from the color array
1156 TypedArray colorRes = context.getResources()
1157 .obtainTypedArray(R.array.camera_mode_theme_color);
1158 if (modeIndex >= colorRes.length() || modeIndex < 0) {
1159 // Mode index not found
1160 Log.e(TAG, "Invalid mode index: " + modeIndex);
1161 return 0;
1162 }
1163 return colorRes.getResourceId(modeIndex, 0);
1164 }
1165
1166 /**
1167 * Gets the mode icon resource id of a specific mode.
1168 *
1169 * @param modeIndex index of the mode
1170 * @param context current context
1171 * @return icon resource id if the index is valid, otherwise 0
1172 */
1173 public static int getCameraModeIconResId(int modeIndex, Context context) {
1174 // Find the camera mode icon using id
1175 TypedArray cameraModesIcons = context.getResources()
1176 .obtainTypedArray(R.array.camera_mode_icon);
1177 if (modeIndex >= cameraModesIcons.length() || modeIndex < 0) {
1178 // Mode index not found
1179 Log.e(TAG, "Invalid mode index: " + modeIndex);
1180 return 0;
1181 }
1182 return cameraModesIcons.getResourceId(modeIndex, 0);
1183 }
1184
1185 /**
1186 * Gets the mode text of a specific mode.
1187 *
1188 * @param modeIndex index of the mode
1189 * @param context current context
1190 * @return mode text if the index is valid, otherwise a new empty string
1191 */
1192 public static String getCameraModeText(int modeIndex, Context context) {
1193 // Find the camera mode icon using id
1194 String[] cameraModesText = context.getResources()
1195 .getStringArray(R.array.camera_mode_text);
1196 if (modeIndex < 0 || modeIndex >= cameraModesText.length) {
1197 Log.e(TAG, "Invalid mode index: " + modeIndex);
1198 return new String();
1199 }
1200 return cameraModesText[modeIndex];
1201 }
Erin Dahlgrenfecd7232014-01-16 13:29:42 -08001202
1203 /**
Spike Sprague79718f62014-01-28 18:48:22 -08001204 * Gets the mode content description of a specific mode.
1205 *
1206 * @param modeIndex index of the mode
1207 * @param context current context
1208 * @return mode content description if the index is valid, otherwise a new empty string
1209 */
1210 public static String getCameraModeContentDescription(int modeIndex, Context context) {
1211 String[] cameraModesDesc = context.getResources()
1212 .getStringArray(R.array.camera_mode_content_description);
1213 if (modeIndex < 0 || modeIndex >= cameraModesDesc.length) {
1214 Log.e(TAG, "Invalid mode index: " + modeIndex);
1215 return new String();
1216 }
1217 return cameraModesDesc[modeIndex];
1218 }
1219
1220 /**
Erin Dahlgrenfecd7232014-01-16 13:29:42 -08001221 * Gets the shutter icon res id for a specific mode.
1222 *
1223 * @param modeIndex index of the mode
1224 * @param context current context
1225 * @return mode shutter icon id if the index is valid, otherwise 0.
1226 */
1227 public static int getCameraShutterIconId(int modeIndex, Context context) {
1228 // Find the camera mode icon using id
1229 TypedArray shutterIcons = context.getResources()
1230 .obtainTypedArray(R.array.camera_mode_shutter_icon);
1231 if (modeIndex < 0 || modeIndex >= shutterIcons.length()) {
1232 Log.e(TAG, "Invalid mode index: " + modeIndex);
1233 return 0;
1234 }
1235 return shutterIcons.getResourceId(modeIndex, 0);
1236 }
Doris Liu213a4a02014-02-04 16:57:55 -08001237
1238 /**
1239 * Gets the parent mode that hosts a specific mode in nav drawer.
1240 *
1241 * @param modeIndex index of the mode
1242 * @param context current context
1243 * @return mode id if the index is valid, otherwise 0
1244 */
1245 public static int getCameraModeParentModeId(int modeIndex, Context context) {
1246 // Find the camera mode icon using id
1247 int[] cameraModeParent = context.getResources()
1248 .getIntArray(R.array.camera_mode_nested_in_nav_drawer);
1249 if (modeIndex < 0 || modeIndex >= cameraModeParent.length) {
1250 Log.e(TAG, "Invalid mode index: " + modeIndex);
1251 return 0;
1252 }
1253 return cameraModeParent[modeIndex];
1254 }
Doris Liub520b972014-02-14 14:14:46 -08001255
1256 /**
1257 * Gets the mode cover icon resource id of a specific mode.
1258 *
1259 * @param modeIndex index of the mode
1260 * @param context current context
1261 * @return icon resource id if the index is valid, otherwise 0
1262 */
1263 public static int getCameraModeCoverIconResId(int modeIndex, Context context) {
1264 // Find the camera mode icon using id
1265 TypedArray cameraModesIcons = context.getResources()
1266 .obtainTypedArray(R.array.camera_mode_cover_icon);
1267 if (modeIndex >= cameraModesIcons.length() || modeIndex < 0) {
1268 // Mode index not found
1269 Log.e(TAG, "Invalid mode index: " + modeIndex);
1270 return 0;
1271 }
1272 return cameraModesIcons.getResourceId(modeIndex, 0);
1273 }
Michael Kolb8872c232013-01-29 10:33:22 -08001274}