blob: 9f4ea91f5cf49a6163a878643753aa18661c2f8f [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;
28import android.graphics.Bitmap;
29import android.graphics.BitmapFactory;
30import android.graphics.Matrix;
31import android.graphics.Point;
32import android.graphics.Rect;
33import android.graphics.RectF;
34import android.hardware.Camera;
35import android.hardware.Camera.CameraInfo;
36import android.hardware.Camera.Parameters;
37import android.hardware.Camera.Size;
38import android.location.Location;
39import android.net.Uri;
Angus Kong4f795b82013-09-16 14:25:35 -070040import android.os.Handler;
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;
Michael Kolb8872c232013-01-29 10:33:22 -080046import android.view.OrientationEventListener;
47import android.view.Surface;
48import android.view.View;
49import android.view.WindowManager;
50import android.view.animation.AlphaAnimation;
51import android.view.animation.Animation;
Angus Kongb40738a2013-06-03 14:55:34 -070052import android.widget.Toast;
Michael Kolb8872c232013-01-29 10:33:22 -080053
Sascha Haeberlingb7639c62013-09-09 14:42:43 -070054import com.android.camera.CameraActivity;
Angus Kongb50b5cb2013-08-09 14:55:20 -070055import com.android.camera.CameraDisabledException;
Angus Kongb50b5cb2013-08-09 14:55:20 -070056import com.android.camera.CameraHolder;
57import com.android.camera.CameraManager;
Mangesh Ghiware2d939fe2013-10-14 21:19:22 -070058import com.android.camera.util.IntentHelper;
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 {
Michael Kolb8872c232013-01-29 10:33:22 -080074 private static final String TAG = "Util";
75
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) {
306 if (c == null) return;
307 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
Angus Kong9ef99252013-07-18 18:04:19 -0700329 public static CameraManager.CameraProxy openCamera(
Angus Kong4f795b82013-09-16 14:25:35 -0700330 Activity activity, final int cameraId,
331 Handler handler, final CameraManager.CameraOpenErrorCallback cb) {
Michael Kolb8872c232013-01-29 10:33:22 -0800332 try {
Angus Kong4f795b82013-09-16 14:25:35 -0700333 throwIfCameraDisabled(activity);
334 return CameraHolder.instance().open(handler, cameraId, cb);
335 } catch (CameraDisabledException ex) {
336 handler.post(new Runnable() {
337 @Override
338 public void run() {
339 cb.onCameraDisabled(cameraId);
340 }
341 });
Michael Kolb8872c232013-01-29 10:33:22 -0800342 }
Angus Kong4f795b82013-09-16 14:25:35 -0700343 return null;
Michael Kolb8872c232013-01-29 10:33:22 -0800344 }
345
346 public static void showErrorAndFinish(final Activity activity, int msgId) {
347 DialogInterface.OnClickListener buttonListener =
348 new DialogInterface.OnClickListener() {
349 @Override
350 public void onClick(DialogInterface dialog, int which) {
351 activity.finish();
352 }
353 };
354 TypedValue out = new TypedValue();
355 activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true);
356 new AlertDialog.Builder(activity)
357 .setCancelable(false)
358 .setTitle(R.string.camera_error_title)
359 .setMessage(msgId)
360 .setNeutralButton(R.string.dialog_ok, buttonListener)
361 .setIcon(out.resourceId)
362 .show();
363 }
364
365 public static <T> T checkNotNull(T object) {
366 if (object == null) throw new NullPointerException();
367 return object;
368 }
369
370 public static boolean equals(Object a, Object b) {
371 return (a == b) || (a == null ? false : a.equals(b));
372 }
373
374 public static int nextPowerOf2(int n) {
375 n -= 1;
376 n |= n >>> 16;
377 n |= n >>> 8;
378 n |= n >>> 4;
379 n |= n >>> 2;
380 n |= n >>> 1;
381 return n + 1;
382 }
383
384 public static float distance(float x, float y, float sx, float sy) {
385 float dx = x - sx;
386 float dy = y - sy;
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700387 return (float) Math.sqrt(dx * dx + dy * dy);
Michael Kolb8872c232013-01-29 10:33:22 -0800388 }
389
390 public static int clamp(int x, int min, int max) {
391 if (x > max) return max;
392 if (x < min) return min;
393 return x;
394 }
395
396 public static int getDisplayRotation(Activity activity) {
397 int rotation = activity.getWindowManager().getDefaultDisplay()
398 .getRotation();
399 switch (rotation) {
400 case Surface.ROTATION_0: return 0;
401 case Surface.ROTATION_90: return 90;
402 case Surface.ROTATION_180: return 180;
403 case Surface.ROTATION_270: return 270;
404 }
405 return 0;
406 }
407
408 public static int getDisplayOrientation(int degrees, int cameraId) {
409 // See android.hardware.Camera.setDisplayOrientation for
410 // documentation.
411 Camera.CameraInfo info = new Camera.CameraInfo();
412 Camera.getCameraInfo(cameraId, info);
413 int result;
414 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
415 result = (info.orientation + degrees) % 360;
416 result = (360 - result) % 360; // compensate the mirror
417 } else { // back-facing
418 result = (info.orientation - degrees + 360) % 360;
419 }
420 return result;
421 }
422
423 public static int getCameraOrientation(int cameraId) {
424 Camera.CameraInfo info = new Camera.CameraInfo();
425 Camera.getCameraInfo(cameraId, info);
426 return info.orientation;
427 }
428
429 public static int roundOrientation(int orientation, int orientationHistory) {
430 boolean changeOrientation = false;
431 if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
432 changeOrientation = true;
433 } else {
434 int dist = Math.abs(orientation - orientationHistory);
435 dist = Math.min( dist, 360 - dist );
436 changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS );
437 }
438 if (changeOrientation) {
439 return ((orientation + 45) / 90 * 90) % 360;
440 }
441 return orientationHistory;
442 }
443
Michael Kolb8872c232013-01-29 10:33:22 -0800444 private static Point getDefaultDisplaySize(Activity activity, Point size) {
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700445 activity.getWindowManager().getDefaultDisplay().getSize(size);
Michael Kolb8872c232013-01-29 10:33:22 -0800446 return size;
447 }
448
449 public static Size getOptimalPreviewSize(Activity currentActivity,
450 List<Size> sizes, double targetRatio) {
Michael Kolb8872c232013-01-29 10:33:22 -0800451
Ruben Brunk714d4d02013-10-09 16:01:16 -0700452 Point[] points = new Point[sizes.size()];
453
454 int index = 0;
455 for (Size s : sizes) {
456 points[index++] = new Point(s.width, s.height);
457 }
458
459 int optimalPickIndex = getOptimalPreviewSize(currentActivity, points, targetRatio);
460 return (optimalPickIndex == -1) ? null : sizes.get(optimalPickIndex);
461 }
462
463 public static int getOptimalPreviewSize(Activity currentActivity,
464 Point[] sizes, double targetRatio) {
465 // Use a very small tolerance because we want an exact match.
466 final double ASPECT_TOLERANCE = 0.01;
467 if (sizes == null) return -1;
468
469 int optimalSizeIndex = -1;
Michael Kolb8872c232013-01-29 10:33:22 -0800470 double minDiff = Double.MAX_VALUE;
471
472 // Because of bugs of overlay and layout, we sometimes will try to
473 // layout the viewfinder in the portrait orientation and thus get the
474 // wrong size of preview surface. When we change the preview size, the
475 // new overlay will be created before the old one closed, which causes
476 // an exception. For now, just get the screen size.
477 Point point = getDefaultDisplaySize(currentActivity, new Point());
478 int targetHeight = Math.min(point.x, point.y);
479 // Try to find an size match aspect ratio and size
Ruben Brunk714d4d02013-10-09 16:01:16 -0700480 for (int i = 0; i < sizes.length; i++) {
481 Point size = sizes[i];
482 double ratio = (double) size.x / size.y;
Michael Kolb8872c232013-01-29 10:33:22 -0800483 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
Ruben Brunk714d4d02013-10-09 16:01:16 -0700484 if (Math.abs(size.y - targetHeight) < minDiff) {
485 optimalSizeIndex = i;
486 minDiff = Math.abs(size.y - targetHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800487 }
488 }
489 // Cannot find the one match the aspect ratio. This should not happen.
490 // Ignore the requirement.
Ruben Brunk714d4d02013-10-09 16:01:16 -0700491 if (optimalSizeIndex == -1) {
Michael Kolb8872c232013-01-29 10:33:22 -0800492 Log.w(TAG, "No preview size match the aspect ratio");
493 minDiff = Double.MAX_VALUE;
Ruben Brunk714d4d02013-10-09 16:01:16 -0700494 for (int i = 0; i < sizes.length; i++) {
495 Point size = sizes[i];
496 if (Math.abs(size.y - targetHeight) < minDiff) {
497 optimalSizeIndex = i;
498 minDiff = Math.abs(size.y - targetHeight);
Michael Kolb8872c232013-01-29 10:33:22 -0800499 }
500 }
501 }
Ruben Brunk714d4d02013-10-09 16:01:16 -0700502 return optimalSizeIndex;
Michael Kolb8872c232013-01-29 10:33:22 -0800503 }
504
505 // Returns the largest picture size which matches the given aspect ratio.
506 public static Size getOptimalVideoSnapshotPictureSize(
507 List<Size> sizes, double targetRatio) {
508 // Use a very small tolerance because we want an exact match.
509 final double ASPECT_TOLERANCE = 0.001;
510 if (sizes == null) return null;
511
512 Size optimalSize = null;
513
514 // Try to find a size matches aspect ratio and has the largest width
515 for (Size size : sizes) {
516 double ratio = (double) size.width / size.height;
517 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
518 if (optimalSize == null || size.width > optimalSize.width) {
519 optimalSize = size;
520 }
521 }
522
523 // Cannot find one that matches the aspect ratio. This should not happen.
524 // Ignore the requirement.
525 if (optimalSize == null) {
526 Log.w(TAG, "No picture size match the aspect ratio");
527 for (Size size : sizes) {
528 if (optimalSize == null || size.width > optimalSize.width) {
529 optimalSize = size;
530 }
531 }
532 }
533 return optimalSize;
534 }
535
536 public static void dumpParameters(Parameters parameters) {
537 String flattened = parameters.flatten();
538 StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
539 Log.d(TAG, "Dump all camera parameters:");
540 while (tokenizer.hasMoreElements()) {
541 Log.d(TAG, tokenizer.nextToken());
542 }
543 }
544
545 /**
546 * Returns whether the device is voice-capable (meaning, it can do MMS).
547 */
548 public static boolean isMmsCapable(Context context) {
549 TelephonyManager telephonyManager = (TelephonyManager)
550 context.getSystemService(Context.TELEPHONY_SERVICE);
551 if (telephonyManager == null) {
552 return false;
553 }
554
555 try {
556 Class<?> partypes[] = new Class[0];
557 Method sIsVoiceCapable = TelephonyManager.class.getMethod(
558 "isVoiceCapable", partypes);
559
560 Object arglist[] = new Object[0];
561 Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist);
562 return (Boolean) retobj;
563 } catch (java.lang.reflect.InvocationTargetException ite) {
564 // Failure, must be another device.
565 // Assume that it is voice capable.
566 } catch (IllegalAccessException iae) {
567 // Failure, must be an other device.
568 // Assume that it is voice capable.
569 } catch (NoSuchMethodException nsme) {
570 }
571 return true;
572 }
573
574 // This is for test only. Allow the camera to launch the specific camera.
575 public static int getCameraFacingIntentExtras(Activity currentActivity) {
576 int cameraId = -1;
577
578 int intentCameraId =
Angus Kongb50b5cb2013-08-09 14:55:20 -0700579 currentActivity.getIntent().getIntExtra(CameraUtil.EXTRAS_CAMERA_FACING, -1);
Michael Kolb8872c232013-01-29 10:33:22 -0800580
581 if (isFrontCameraIntent(intentCameraId)) {
582 // Check if the front camera exist
583 int frontCameraId = CameraHolder.instance().getFrontCameraId();
584 if (frontCameraId != -1) {
585 cameraId = frontCameraId;
586 }
587 } else if (isBackCameraIntent(intentCameraId)) {
588 // Check if the back camera exist
589 int backCameraId = CameraHolder.instance().getBackCameraId();
590 if (backCameraId != -1) {
591 cameraId = backCameraId;
592 }
593 }
594 return cameraId;
595 }
596
597 private static boolean isFrontCameraIntent(int intentCameraId) {
598 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
599 }
600
601 private static boolean isBackCameraIntent(int intentCameraId) {
602 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
603 }
604
605 private static int sLocation[] = new int[2];
606
607 // This method is not thread-safe.
608 public static boolean pointInView(float x, float y, View v) {
609 v.getLocationInWindow(sLocation);
610 return x >= sLocation[0] && x < (sLocation[0] + v.getWidth())
611 && y >= sLocation[1] && y < (sLocation[1] + v.getHeight());
612 }
613
614 public static int[] getRelativeLocation(View reference, View view) {
615 reference.getLocationInWindow(sLocation);
616 int referenceX = sLocation[0];
617 int referenceY = sLocation[1];
618 view.getLocationInWindow(sLocation);
619 sLocation[0] -= referenceX;
620 sLocation[1] -= referenceY;
621 return sLocation;
622 }
623
624 public static boolean isUriValid(Uri uri, ContentResolver resolver) {
625 if (uri == null) return false;
626
627 try {
628 ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
629 if (pfd == null) {
630 Log.e(TAG, "Fail to open URI. URI=" + uri);
631 return false;
632 }
633 pfd.close();
634 } catch (IOException ex) {
635 return false;
636 }
637 return true;
638 }
639
Michael Kolb8872c232013-01-29 10:33:22 -0800640 public static void dumpRect(RectF rect, String msg) {
641 Log.v(TAG, msg + "=(" + rect.left + "," + rect.top
642 + "," + rect.right + "," + rect.bottom + ")");
643 }
644
645 public static void rectFToRect(RectF rectF, Rect rect) {
646 rect.left = Math.round(rectF.left);
647 rect.top = Math.round(rectF.top);
648 rect.right = Math.round(rectF.right);
649 rect.bottom = Math.round(rectF.bottom);
650 }
651
652 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
653 int viewWidth, int viewHeight) {
654 // Need mirror for front camera.
655 matrix.setScale(mirror ? -1 : 1, 1);
656 // This is the value for android.hardware.Camera.setDisplayOrientation.
657 matrix.postRotate(displayOrientation);
658 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
659 // UI coordinates range from (0, 0) to (width, height).
660 matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
661 matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
662 }
663
664 public static String createJpegName(long dateTaken) {
665 synchronized (sImageFileNamer) {
666 return sImageFileNamer.generateName(dateTaken);
667 }
668 }
669
670 public static void broadcastNewPicture(Context context, Uri uri) {
671 context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri));
672 // Keep compatibility
673 context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri));
674 }
675
676 public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) {
677 if (view.getVisibility() == View.VISIBLE) return;
678
679 view.setVisibility(View.VISIBLE);
680 Animation animation = new AlphaAnimation(startAlpha, endAlpha);
681 animation.setDuration(duration);
682 view.startAnimation(animation);
683 }
684
685 public static void fadeIn(View view) {
686 fadeIn(view, 0F, 1F, 400);
687
688 // We disabled the button in fadeOut(), so enable it here.
689 view.setEnabled(true);
690 }
691
692 public static void fadeOut(View view) {
693 if (view.getVisibility() != View.VISIBLE) return;
694
695 // Since the button is still clickable before fade-out animation
696 // ends, we disable the button first to block click.
697 view.setEnabled(false);
698 Animation animation = new AlphaAnimation(1F, 0F);
699 animation.setDuration(400);
700 view.startAnimation(animation);
701 view.setVisibility(View.GONE);
702 }
703
704 public static int getJpegRotation(int cameraId, int orientation) {
705 // See android.hardware.Camera.Parameters.setRotation for
706 // documentation.
707 int rotation = 0;
708 if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
709 CameraInfo info = CameraHolder.instance().getCameraInfo()[cameraId];
710 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
711 rotation = (info.orientation - orientation + 360) % 360;
712 } else { // back-facing camera
713 rotation = (info.orientation + orientation) % 360;
714 }
715 }
716 return rotation;
717 }
718
Sascha Haeberling37f36112013-08-06 14:31:52 -0700719 /**
720 * Down-samples a jpeg byte array.
721 * @param data a byte array of jpeg data
722 * @param downSampleFactor down-sample factor
723 * @return decoded and down-sampled bitmap
724 */
725 public static Bitmap downSample(final byte[] data, int downSampleFactor) {
726 final BitmapFactory.Options opts = new BitmapFactory.Options();
727 // Downsample the image
728 opts.inSampleSize = downSampleFactor;
729 return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
730 }
731
Michael Kolb8872c232013-01-29 10:33:22 -0800732 public static void setGpsParameters(Parameters parameters, Location loc) {
733 // Clear previous GPS location from the parameters.
734 parameters.removeGpsData();
735
736 // We always encode GpsTimeStamp
737 parameters.setGpsTimestamp(System.currentTimeMillis() / 1000);
738
739 // Set GPS location.
740 if (loc != null) {
741 double lat = loc.getLatitude();
742 double lon = loc.getLongitude();
743 boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
744
745 if (hasLatLon) {
746 Log.d(TAG, "Set gps location");
747 parameters.setGpsLatitude(lat);
748 parameters.setGpsLongitude(lon);
749 parameters.setGpsProcessingMethod(loc.getProvider().toUpperCase());
750 if (loc.hasAltitude()) {
751 parameters.setGpsAltitude(loc.getAltitude());
752 } else {
753 // for NETWORK_PROVIDER location provider, we may have
754 // no altitude information, but the driver needs it, so
755 // we fake one.
756 parameters.setGpsAltitude(0);
757 }
758 if (loc.getTime() != 0) {
759 // Location.getTime() is UTC in milliseconds.
760 // gps-timestamp is UTC in seconds.
761 long utcTimeSeconds = loc.getTime() / 1000;
762 parameters.setGpsTimestamp(utcTimeSeconds);
763 }
764 } else {
765 loc = null;
766 }
767 }
768 }
769
ztenghui16a35202013-09-23 11:35:36 -0700770 /**
771 * For still image capture, we need to get the right fps range such that the
772 * camera can slow down the framerate to allow for less-noisy/dark
773 * viewfinder output in dark conditions.
774 *
775 * @param params Camera's parameters.
776 * @return null if no appropiate fps range can't be found. Otherwise, return
777 * the right range.
778 */
779 public static int[] getPhotoPreviewFpsRange(Parameters params) {
780 List<int[]> frameRates = params.getSupportedPreviewFpsRange();
781 if (frameRates.size() == 0) {
782 Log.e(TAG, "No suppoted frame rates returned!");
783 return null;
784 }
785
786 // Find the lowest min rate in supported ranges who can cover 30fps.
787 int lowestMinRate = MAX_PREVIEW_FPS_TIMES_1000;
788 for (int[] rate : frameRates) {
789 int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX];
790 int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX];
791 if (maxFps >= PREFERRED_PREVIEW_FPS_TIMES_1000 &&
792 minFps <= PREFERRED_PREVIEW_FPS_TIMES_1000 &&
793 minFps < lowestMinRate) {
794 lowestMinRate = minFps;
795 }
796 }
797
798 // Find all the modes with the lowest min rate found above, the pick the
799 // one with highest max rate.
800 int resultIndex = -1;
801 int highestMaxRate = 0;
802 for (int i = 0; i < frameRates.size(); i++) {
803 int[] rate = frameRates.get(i);
804 int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX];
805 int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX];
806 if (minFps == lowestMinRate && highestMaxRate < maxFps) {
807 highestMaxRate = maxFps;
808 resultIndex = i;
809 }
810 }
811
812 if (resultIndex >= 0) {
813 return frameRates.get(resultIndex);
814 }
815 Log.e(TAG, "Can't find an appropiate frame rate range!");
816 return null;
817 }
Angus Kongd82eae22013-06-11 12:00:15 -0700818
819 public static int[] getMaxPreviewFpsRange(Parameters params) {
820 List<int[]> frameRates = params.getSupportedPreviewFpsRange();
821 if (frameRates != null && frameRates.size() > 0) {
822 // The list is sorted. Return the last element.
823 return frameRates.get(frameRates.size() - 1);
824 }
825 return new int[0];
826 }
827
Michael Kolb8872c232013-01-29 10:33:22 -0800828 private static class ImageFileNamer {
829 private SimpleDateFormat mFormat;
830
831 // The date (in milliseconds) used to generate the last name.
832 private long mLastDate;
833
834 // Number of names generated for the same second.
835 private int mSameSecondCount;
836
837 public ImageFileNamer(String format) {
838 mFormat = new SimpleDateFormat(format);
839 }
840
841 public String generateName(long dateTaken) {
842 Date date = new Date(dateTaken);
843 String result = mFormat.format(date);
844
845 // If the last name was generated for the same second,
846 // we append _1, _2, etc to the name.
847 if (dateTaken / 1000 == mLastDate / 1000) {
848 mSameSecondCount++;
849 result += "_" + mSameSecondCount;
850 } else {
851 mLastDate = dateTaken;
852 mSameSecondCount = 0;
853 }
854
855 return result;
856 }
857 }
Angus Kongb40738a2013-06-03 14:55:34 -0700858
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700859 public static void playVideo(Activity activity, Uri uri, String title) {
Angus Kongb40738a2013-06-03 14:55:34 -0700860 try {
ztenghui98b2a282013-10-11 17:15:40 -0700861 boolean isSecureCamera = ((CameraActivity)activity).isSecureCamera();
862 if (!isSecureCamera) {
Mangesh Ghiware2d939fe2013-10-14 21:19:22 -0700863 Intent intent = IntentHelper.getVideoPlayerIntent(activity, uri)
ztenghui98b2a282013-10-11 17:15:40 -0700864 .putExtra(Intent.EXTRA_TITLE, title)
865 .putExtra(KEY_TREAT_UP_AS_BACK, true);
866 activity.startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
867 } else {
868 // In order not to send out any intent to be intercepted and
869 // show the lock screen immediately, we just let the secure
870 // camera activity finish.
871 activity.finish();
872 }
Angus Kongb40738a2013-06-03 14:55:34 -0700873 } catch (ActivityNotFoundException e) {
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700874 Toast.makeText(activity, activity.getString(R.string.video_err),
Angus Kongb40738a2013-06-03 14:55:34 -0700875 Toast.LENGTH_SHORT).show();
876 }
877 }
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700878
879 /**
880 * Starts GMM with the given location shown. If this fails, and GMM could
881 * not be found, we use a geo intent as a fallback.
882 *
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700883 * @param activity the activity to use for launching the Maps intent.
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700884 * @param latLong a 2-element array containing {latitude/longitude}.
885 */
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700886 public static void showOnMap(Activity activity, double[] latLong) {
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700887 try {
888 // We don't use "geo:latitude,longitude" because it only centers
889 // the MapView to the specified location, but we need a marker
890 // for further operations (routing to/from).
891 // The q=(lat, lng) syntax is suggested by geo-team.
892 String uri = String.format(Locale.ENGLISH, "http://maps.google.com/maps?f=q&q=(%f,%f)",
893 latLong[0], latLong[1]);
894 ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME,
895 MAPS_CLASS_NAME);
896 Intent mapsIntent = new Intent(Intent.ACTION_VIEW,
897 Uri.parse(uri)).setComponent(compName);
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700898 activity.startActivityForResult(mapsIntent,
899 CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700900 } catch (ActivityNotFoundException e) {
901 // Use the "geo intent" if no GMM is installed
902 Log.e(TAG, "GMM activity not found!", e);
903 String url = String.format(Locale.ENGLISH, "geo:%f,%f", latLong[0], latLong[1]);
904 Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700905 activity.startActivity(mapsIntent);
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700906 }
907 }
Angus Kong7d2388d2013-09-18 23:56:01 -0700908
Angus Kongb21215a2013-09-20 18:41:46 -0700909 /**
910 * Dumps the stack trace.
911 *
912 * @param level How many levels of the stack are dumped. 0 means all.
913 * @return A {@link java.lang.String} of all the output with newline
914 * between each.
915 */
Angus Kong7d2388d2013-09-18 23:56:01 -0700916 public static String dumpStackTrace(int level) {
917 StackTraceElement[] elems = Thread.currentThread().getStackTrace();
918 // Ignore the first 3 elements.
Angus Kongb21215a2013-09-20 18:41:46 -0700919 level = (level == 0 ? elems.length : Math.min(level + 3, elems.length));
Angus Kong7d2388d2013-09-18 23:56:01 -0700920 String ret = new String();
921 for (int i = 3; i < level; i++) {
Angus Kongb21215a2013-09-20 18:41:46 -0700922 ret = ret + "\t" + elems[i].toString() + '\n';
Angus Kong7d2388d2013-09-18 23:56:01 -0700923 }
924 return ret;
925 }
Angus Kong690dc472013-09-21 14:48:51 -0700926
927 /**
928 * Launches apps supporting action {@link Intent.ACTION_MAIN} of category
929 * {@link Intent.CATEGORY_APP_GALLERY}. Note that
930 * {@link Intent.CATEGORY_APP_GALLERY} is only available on API level 15+.
931 *
932 * @param ctx The {@link android.content.Context} to launch the app.
933 * @return {@code true} on success.
934 */
935 public static boolean launchGallery(Context ctx) {
936 if (ApiHelper.HAS_APP_GALLERY) {
Mangesh Ghiware2d939fe2013-10-14 21:19:22 -0700937 ctx.startActivity(IntentHelper.getGalleryIntent(ctx));
Angus Kong690dc472013-09-21 14:48:51 -0700938 return true;
939 }
940 return false;
941 }
Michael Kolb8872c232013-01-29 10:33:22 -0800942}