blob: 2ca32f0bb79745915f6b555a1f9de42b013a360c [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;
Sascha Haeberling37f36112013-08-06 14:31:52 -070058import com.android.camera2.R;
Michael Kolb8872c232013-01-29 10:33:22 -080059
Sascha Haeberling638e6f02013-09-18 14:28:51 -070060import java.io.Closeable;
61import java.io.IOException;
62import java.lang.reflect.Method;
63import java.text.SimpleDateFormat;
64import java.util.Date;
65import java.util.List;
66import java.util.Locale;
67import java.util.StringTokenizer;
68
Michael Kolb8872c232013-01-29 10:33:22 -080069/**
70 * Collection of utility functions used in this package.
71 */
Angus Kongb50b5cb2013-08-09 14:55:20 -070072public class CameraUtil {
Michael Kolb8872c232013-01-29 10:33:22 -080073 private static final String TAG = "Util";
74
Angus Kongb50b5cb2013-08-09 14:55:20 -070075 // For creating crop intents.
76 public static final String KEY_RETURN_DATA = "return-data";
77 public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked";
78
Michael Kolb8872c232013-01-29 10:33:22 -080079 // Orientation hysteresis amount used in rounding, in degrees
80 public static final int ORIENTATION_HYSTERESIS = 5;
81
82 public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW";
83 // See android.hardware.Camera.ACTION_NEW_PICTURE.
84 public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
85 // See android.hardware.Camera.ACTION_NEW_VIDEO.
86 public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
87
Dan Aminzadefca1c5e2013-08-14 17:36:56 -070088 // Broadcast Action: The camera application has become active in picture-taking mode.
89 public static final String ACTION_CAMERA_STARTED = "com.android.camera.action.CAMERA_STARTED";
90 // Broadcast Action: The camera application is no longer in active picture-taking mode.
91 public static final String ACTION_CAMERA_STOPPED = "com.android.camera.action.CAMERA_STOPPED";
92 // When the camera application is active in picture-taking mode, it listens for this intent,
93 // which upon receipt will trigger the shutter to capture a new picture, as if the user had
94 // pressed the shutter button.
95 public static final String ACTION_CAMERA_SHUTTER_CLICK =
96 "com.android.camera.action.SHUTTER_CLICK";
97
Michael Kolb8872c232013-01-29 10:33:22 -080098 // Fields from android.hardware.Camera.Parameters
99 public static final String FOCUS_MODE_CONTINUOUS_PICTURE = "continuous-picture";
100 public static final String RECORDING_HINT = "recording-hint";
101 private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
102 private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported";
103 private static final String VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported";
104 public static final String SCENE_MODE_HDR = "hdr";
105 public static final String TRUE = "true";
106 public static final String FALSE = "false";
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700107
108 // Fields for the show-on-maps-functionality
109 private static final String MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
110 private static final String MAPS_CLASS_NAME = "com.google.android.maps.MapsActivity";
111
112 /** Has to be in sync with the receiving MovieActivity. */
113 public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back";
Michael Kolb8872c232013-01-29 10:33:22 -0800114
115 public static boolean isSupported(String value, List<String> supported) {
116 return supported == null ? false : supported.indexOf(value) >= 0;
117 }
118
119 public static boolean isAutoExposureLockSupported(Parameters params) {
120 return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
121 }
122
123 public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
124 return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
125 }
126
127 public static boolean isVideoSnapshotSupported(Parameters params) {
128 return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED));
129 }
130
131 public static boolean isCameraHdrSupported(Parameters params) {
132 List<String> supported = params.getSupportedSceneModes();
133 return (supported != null) && supported.contains(SCENE_MODE_HDR);
134 }
135
Michael Kolb8872c232013-01-29 10:33:22 -0800136 public static boolean isMeteringAreaSupported(Parameters params) {
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700137 return params.getMaxNumMeteringAreas() > 0;
Michael Kolb8872c232013-01-29 10:33:22 -0800138 }
139
Michael Kolb8872c232013-01-29 10:33:22 -0800140 public static boolean isFocusAreaSupported(Parameters params) {
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700141 return (params.getMaxNumFocusAreas() > 0
142 && isSupported(Parameters.FOCUS_MODE_AUTO,
143 params.getSupportedFocusModes()));
Michael Kolb8872c232013-01-29 10:33:22 -0800144 }
145
146 // Private intent extras. Test only.
147 private static final String EXTRAS_CAMERA_FACING =
148 "android.intent.extras.CAMERA_FACING";
149
150 private static float sPixelDensity = 1;
151 private static ImageFileNamer sImageFileNamer;
152
Angus Kongb50b5cb2013-08-09 14:55:20 -0700153 private CameraUtil() {
Michael Kolb8872c232013-01-29 10:33:22 -0800154 }
155
156 public static void initialize(Context context) {
157 DisplayMetrics metrics = new DisplayMetrics();
158 WindowManager wm = (WindowManager)
159 context.getSystemService(Context.WINDOW_SERVICE);
160 wm.getDefaultDisplay().getMetrics(metrics);
161 sPixelDensity = metrics.density;
162 sImageFileNamer = new ImageFileNamer(
163 context.getString(R.string.image_file_name_format));
164 }
165
166 public static int dpToPixel(int dp) {
167 return Math.round(sPixelDensity * dp);
168 }
169
170 // Rotates the bitmap by the specified degree.
171 // If a new bitmap is created, the original bitmap is recycled.
172 public static Bitmap rotate(Bitmap b, int degrees) {
173 return rotateAndMirror(b, degrees, false);
174 }
175
176 // Rotates and/or mirrors the bitmap. If a new bitmap is created, the
177 // original bitmap is recycled.
178 public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) {
179 if ((degrees != 0 || mirror) && b != null) {
180 Matrix m = new Matrix();
181 // Mirror first.
182 // horizontal flip + rotation = -rotation + horizontal flip
183 if (mirror) {
184 m.postScale(-1, 1);
185 degrees = (degrees + 360) % 360;
186 if (degrees == 0 || degrees == 180) {
187 m.postTranslate(b.getWidth(), 0);
188 } else if (degrees == 90 || degrees == 270) {
189 m.postTranslate(b.getHeight(), 0);
190 } else {
191 throw new IllegalArgumentException("Invalid degrees=" + degrees);
192 }
193 }
194 if (degrees != 0) {
195 // clockwise
196 m.postRotate(degrees,
197 (float) b.getWidth() / 2, (float) b.getHeight() / 2);
198 }
199
200 try {
201 Bitmap b2 = Bitmap.createBitmap(
202 b, 0, 0, b.getWidth(), b.getHeight(), m, true);
203 if (b != b2) {
204 b.recycle();
205 b = b2;
206 }
207 } catch (OutOfMemoryError ex) {
208 // We have no memory to rotate. Return the original bitmap.
209 }
210 }
211 return b;
212 }
213
214 /*
215 * Compute the sample size as a function of minSideLength
216 * and maxNumOfPixels.
217 * minSideLength is used to specify that minimal width or height of a
218 * bitmap.
219 * maxNumOfPixels is used to specify the maximal size in pixels that is
220 * tolerable in terms of memory usage.
221 *
222 * The function returns a sample size based on the constraints.
223 * Both size and minSideLength can be passed in as -1
224 * which indicates no care of the corresponding constraint.
225 * The functions prefers returning a sample size that
226 * generates a smaller bitmap, unless minSideLength = -1.
227 *
228 * Also, the function rounds up the sample size to a power of 2 or multiple
229 * of 8 because BitmapFactory only honors sample size this way.
230 * For example, BitmapFactory downsamples an image by 2 even though the
231 * request is 3. So we round up the sample size to avoid OOM.
232 */
233 public static int computeSampleSize(BitmapFactory.Options options,
234 int minSideLength, int maxNumOfPixels) {
235 int initialSize = computeInitialSampleSize(options, minSideLength,
236 maxNumOfPixels);
237
238 int roundedSize;
239 if (initialSize <= 8) {
240 roundedSize = 1;
241 while (roundedSize < initialSize) {
242 roundedSize <<= 1;
243 }
244 } else {
245 roundedSize = (initialSize + 7) / 8 * 8;
246 }
247
248 return roundedSize;
249 }
250
251 private static int computeInitialSampleSize(BitmapFactory.Options options,
252 int minSideLength, int maxNumOfPixels) {
253 double w = options.outWidth;
254 double h = options.outHeight;
255
256 int lowerBound = (maxNumOfPixels < 0) ? 1 :
257 (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
258 int upperBound = (minSideLength < 0) ? 128 :
259 (int) Math.min(Math.floor(w / minSideLength),
260 Math.floor(h / minSideLength));
261
262 if (upperBound < lowerBound) {
263 // return the larger one when there is no overlapping zone.
264 return lowerBound;
265 }
266
267 if (maxNumOfPixels < 0 && minSideLength < 0) {
268 return 1;
269 } else if (minSideLength < 0) {
270 return lowerBound;
271 } else {
272 return upperBound;
273 }
274 }
275
276 public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) {
277 try {
278 BitmapFactory.Options options = new BitmapFactory.Options();
279 options.inJustDecodeBounds = true;
280 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
281 options);
282 if (options.mCancel || options.outWidth == -1
283 || options.outHeight == -1) {
284 return null;
285 }
286 options.inSampleSize = computeSampleSize(
287 options, -1, maxNumOfPixels);
288 options.inJustDecodeBounds = false;
289
290 options.inDither = false;
291 options.inPreferredConfig = Bitmap.Config.ARGB_8888;
292 return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
293 options);
294 } catch (OutOfMemoryError ex) {
295 Log.e(TAG, "Got oom exception ", ex);
296 return null;
297 }
298 }
299
300 public static void closeSilently(Closeable c) {
301 if (c == null) return;
302 try {
303 c.close();
304 } catch (Throwable t) {
305 // do nothing
306 }
307 }
308
309 public static void Assert(boolean cond) {
310 if (!cond) {
311 throw new AssertionError();
312 }
313 }
314
Michael Kolb8872c232013-01-29 10:33:22 -0800315 private static void throwIfCameraDisabled(Activity activity) throws CameraDisabledException {
316 // Check if device policy has disabled the camera.
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700317 DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService(
318 Context.DEVICE_POLICY_SERVICE);
319 if (dpm.getCameraDisabled(null)) {
320 throw new CameraDisabledException();
Michael Kolb8872c232013-01-29 10:33:22 -0800321 }
322 }
323
Angus Kong9ef99252013-07-18 18:04:19 -0700324 public static CameraManager.CameraProxy openCamera(
Angus Kong4f795b82013-09-16 14:25:35 -0700325 Activity activity, final int cameraId,
326 Handler handler, final CameraManager.CameraOpenErrorCallback cb) {
Michael Kolb8872c232013-01-29 10:33:22 -0800327 try {
Angus Kong4f795b82013-09-16 14:25:35 -0700328 throwIfCameraDisabled(activity);
329 return CameraHolder.instance().open(handler, cameraId, cb);
330 } catch (CameraDisabledException ex) {
331 handler.post(new Runnable() {
332 @Override
333 public void run() {
334 cb.onCameraDisabled(cameraId);
335 }
336 });
Michael Kolb8872c232013-01-29 10:33:22 -0800337 }
Angus Kong4f795b82013-09-16 14:25:35 -0700338 return null;
Michael Kolb8872c232013-01-29 10:33:22 -0800339 }
340
341 public static void showErrorAndFinish(final Activity activity, int msgId) {
342 DialogInterface.OnClickListener buttonListener =
343 new DialogInterface.OnClickListener() {
344 @Override
345 public void onClick(DialogInterface dialog, int which) {
346 activity.finish();
347 }
348 };
349 TypedValue out = new TypedValue();
350 activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true);
351 new AlertDialog.Builder(activity)
352 .setCancelable(false)
353 .setTitle(R.string.camera_error_title)
354 .setMessage(msgId)
355 .setNeutralButton(R.string.dialog_ok, buttonListener)
356 .setIcon(out.resourceId)
357 .show();
358 }
359
360 public static <T> T checkNotNull(T object) {
361 if (object == null) throw new NullPointerException();
362 return object;
363 }
364
365 public static boolean equals(Object a, Object b) {
366 return (a == b) || (a == null ? false : a.equals(b));
367 }
368
369 public static int nextPowerOf2(int n) {
370 n -= 1;
371 n |= n >>> 16;
372 n |= n >>> 8;
373 n |= n >>> 4;
374 n |= n >>> 2;
375 n |= n >>> 1;
376 return n + 1;
377 }
378
379 public static float distance(float x, float y, float sx, float sy) {
380 float dx = x - sx;
381 float dy = y - sy;
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700382 return (float) Math.sqrt(dx * dx + dy * dy);
Michael Kolb8872c232013-01-29 10:33:22 -0800383 }
384
385 public static int clamp(int x, int min, int max) {
386 if (x > max) return max;
387 if (x < min) return min;
388 return x;
389 }
390
391 public static int getDisplayRotation(Activity activity) {
392 int rotation = activity.getWindowManager().getDefaultDisplay()
393 .getRotation();
394 switch (rotation) {
395 case Surface.ROTATION_0: return 0;
396 case Surface.ROTATION_90: return 90;
397 case Surface.ROTATION_180: return 180;
398 case Surface.ROTATION_270: return 270;
399 }
400 return 0;
401 }
402
403 public static int getDisplayOrientation(int degrees, int cameraId) {
404 // See android.hardware.Camera.setDisplayOrientation for
405 // documentation.
406 Camera.CameraInfo info = new Camera.CameraInfo();
407 Camera.getCameraInfo(cameraId, info);
408 int result;
409 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
410 result = (info.orientation + degrees) % 360;
411 result = (360 - result) % 360; // compensate the mirror
412 } else { // back-facing
413 result = (info.orientation - degrees + 360) % 360;
414 }
415 return result;
416 }
417
418 public static int getCameraOrientation(int cameraId) {
419 Camera.CameraInfo info = new Camera.CameraInfo();
420 Camera.getCameraInfo(cameraId, info);
421 return info.orientation;
422 }
423
424 public static int roundOrientation(int orientation, int orientationHistory) {
425 boolean changeOrientation = false;
426 if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
427 changeOrientation = true;
428 } else {
429 int dist = Math.abs(orientation - orientationHistory);
430 dist = Math.min( dist, 360 - dist );
431 changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS );
432 }
433 if (changeOrientation) {
434 return ((orientation + 45) / 90 * 90) % 360;
435 }
436 return orientationHistory;
437 }
438
Michael Kolb8872c232013-01-29 10:33:22 -0800439 private static Point getDefaultDisplaySize(Activity activity, Point size) {
Sascha Haeberling638e6f02013-09-18 14:28:51 -0700440 activity.getWindowManager().getDefaultDisplay().getSize(size);
Michael Kolb8872c232013-01-29 10:33:22 -0800441 return size;
442 }
443
444 public static Size getOptimalPreviewSize(Activity currentActivity,
445 List<Size> sizes, double targetRatio) {
446 // Use a very small tolerance because we want an exact match.
447 final double ASPECT_TOLERANCE = 0.001;
448 if (sizes == null) return null;
449
450 Size optimalSize = null;
451 double minDiff = Double.MAX_VALUE;
452
453 // Because of bugs of overlay and layout, we sometimes will try to
454 // layout the viewfinder in the portrait orientation and thus get the
455 // wrong size of preview surface. When we change the preview size, the
456 // new overlay will be created before the old one closed, which causes
457 // an exception. For now, just get the screen size.
458 Point point = getDefaultDisplaySize(currentActivity, new Point());
459 int targetHeight = Math.min(point.x, point.y);
460 // Try to find an size match aspect ratio and size
461 for (Size size : sizes) {
462 double ratio = (double) size.width / size.height;
463 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
464 if (Math.abs(size.height - targetHeight) < minDiff) {
465 optimalSize = size;
466 minDiff = Math.abs(size.height - targetHeight);
467 }
468 }
469 // Cannot find the one match the aspect ratio. This should not happen.
470 // Ignore the requirement.
471 if (optimalSize == null) {
472 Log.w(TAG, "No preview size match the aspect ratio");
473 minDiff = Double.MAX_VALUE;
474 for (Size size : sizes) {
475 if (Math.abs(size.height - targetHeight) < minDiff) {
476 optimalSize = size;
477 minDiff = Math.abs(size.height - targetHeight);
478 }
479 }
480 }
481 return optimalSize;
482 }
483
484 // Returns the largest picture size which matches the given aspect ratio.
485 public static Size getOptimalVideoSnapshotPictureSize(
486 List<Size> sizes, double targetRatio) {
487 // Use a very small tolerance because we want an exact match.
488 final double ASPECT_TOLERANCE = 0.001;
489 if (sizes == null) return null;
490
491 Size optimalSize = null;
492
493 // Try to find a size matches aspect ratio and has the largest width
494 for (Size size : sizes) {
495 double ratio = (double) size.width / size.height;
496 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
497 if (optimalSize == null || size.width > optimalSize.width) {
498 optimalSize = size;
499 }
500 }
501
502 // Cannot find one that matches the aspect ratio. This should not happen.
503 // Ignore the requirement.
504 if (optimalSize == null) {
505 Log.w(TAG, "No picture size match the aspect ratio");
506 for (Size size : sizes) {
507 if (optimalSize == null || size.width > optimalSize.width) {
508 optimalSize = size;
509 }
510 }
511 }
512 return optimalSize;
513 }
514
515 public static void dumpParameters(Parameters parameters) {
516 String flattened = parameters.flatten();
517 StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
518 Log.d(TAG, "Dump all camera parameters:");
519 while (tokenizer.hasMoreElements()) {
520 Log.d(TAG, tokenizer.nextToken());
521 }
522 }
523
524 /**
525 * Returns whether the device is voice-capable (meaning, it can do MMS).
526 */
527 public static boolean isMmsCapable(Context context) {
528 TelephonyManager telephonyManager = (TelephonyManager)
529 context.getSystemService(Context.TELEPHONY_SERVICE);
530 if (telephonyManager == null) {
531 return false;
532 }
533
534 try {
535 Class<?> partypes[] = new Class[0];
536 Method sIsVoiceCapable = TelephonyManager.class.getMethod(
537 "isVoiceCapable", partypes);
538
539 Object arglist[] = new Object[0];
540 Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist);
541 return (Boolean) retobj;
542 } catch (java.lang.reflect.InvocationTargetException ite) {
543 // Failure, must be another device.
544 // Assume that it is voice capable.
545 } catch (IllegalAccessException iae) {
546 // Failure, must be an other device.
547 // Assume that it is voice capable.
548 } catch (NoSuchMethodException nsme) {
549 }
550 return true;
551 }
552
553 // This is for test only. Allow the camera to launch the specific camera.
554 public static int getCameraFacingIntentExtras(Activity currentActivity) {
555 int cameraId = -1;
556
557 int intentCameraId =
Angus Kongb50b5cb2013-08-09 14:55:20 -0700558 currentActivity.getIntent().getIntExtra(CameraUtil.EXTRAS_CAMERA_FACING, -1);
Michael Kolb8872c232013-01-29 10:33:22 -0800559
560 if (isFrontCameraIntent(intentCameraId)) {
561 // Check if the front camera exist
562 int frontCameraId = CameraHolder.instance().getFrontCameraId();
563 if (frontCameraId != -1) {
564 cameraId = frontCameraId;
565 }
566 } else if (isBackCameraIntent(intentCameraId)) {
567 // Check if the back camera exist
568 int backCameraId = CameraHolder.instance().getBackCameraId();
569 if (backCameraId != -1) {
570 cameraId = backCameraId;
571 }
572 }
573 return cameraId;
574 }
575
576 private static boolean isFrontCameraIntent(int intentCameraId) {
577 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
578 }
579
580 private static boolean isBackCameraIntent(int intentCameraId) {
581 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
582 }
583
584 private static int sLocation[] = new int[2];
585
586 // This method is not thread-safe.
587 public static boolean pointInView(float x, float y, View v) {
588 v.getLocationInWindow(sLocation);
589 return x >= sLocation[0] && x < (sLocation[0] + v.getWidth())
590 && y >= sLocation[1] && y < (sLocation[1] + v.getHeight());
591 }
592
593 public static int[] getRelativeLocation(View reference, View view) {
594 reference.getLocationInWindow(sLocation);
595 int referenceX = sLocation[0];
596 int referenceY = sLocation[1];
597 view.getLocationInWindow(sLocation);
598 sLocation[0] -= referenceX;
599 sLocation[1] -= referenceY;
600 return sLocation;
601 }
602
603 public static boolean isUriValid(Uri uri, ContentResolver resolver) {
604 if (uri == null) return false;
605
606 try {
607 ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
608 if (pfd == null) {
609 Log.e(TAG, "Fail to open URI. URI=" + uri);
610 return false;
611 }
612 pfd.close();
613 } catch (IOException ex) {
614 return false;
615 }
616 return true;
617 }
618
Michael Kolb8872c232013-01-29 10:33:22 -0800619 public static void dumpRect(RectF rect, String msg) {
620 Log.v(TAG, msg + "=(" + rect.left + "," + rect.top
621 + "," + rect.right + "," + rect.bottom + ")");
622 }
623
624 public static void rectFToRect(RectF rectF, Rect rect) {
625 rect.left = Math.round(rectF.left);
626 rect.top = Math.round(rectF.top);
627 rect.right = Math.round(rectF.right);
628 rect.bottom = Math.round(rectF.bottom);
629 }
630
631 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
632 int viewWidth, int viewHeight) {
633 // Need mirror for front camera.
634 matrix.setScale(mirror ? -1 : 1, 1);
635 // This is the value for android.hardware.Camera.setDisplayOrientation.
636 matrix.postRotate(displayOrientation);
637 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
638 // UI coordinates range from (0, 0) to (width, height).
639 matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
640 matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
641 }
642
643 public static String createJpegName(long dateTaken) {
644 synchronized (sImageFileNamer) {
645 return sImageFileNamer.generateName(dateTaken);
646 }
647 }
648
649 public static void broadcastNewPicture(Context context, Uri uri) {
650 context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri));
651 // Keep compatibility
652 context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri));
653 }
654
655 public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) {
656 if (view.getVisibility() == View.VISIBLE) return;
657
658 view.setVisibility(View.VISIBLE);
659 Animation animation = new AlphaAnimation(startAlpha, endAlpha);
660 animation.setDuration(duration);
661 view.startAnimation(animation);
662 }
663
664 public static void fadeIn(View view) {
665 fadeIn(view, 0F, 1F, 400);
666
667 // We disabled the button in fadeOut(), so enable it here.
668 view.setEnabled(true);
669 }
670
671 public static void fadeOut(View view) {
672 if (view.getVisibility() != View.VISIBLE) return;
673
674 // Since the button is still clickable before fade-out animation
675 // ends, we disable the button first to block click.
676 view.setEnabled(false);
677 Animation animation = new AlphaAnimation(1F, 0F);
678 animation.setDuration(400);
679 view.startAnimation(animation);
680 view.setVisibility(View.GONE);
681 }
682
683 public static int getJpegRotation(int cameraId, int orientation) {
684 // See android.hardware.Camera.Parameters.setRotation for
685 // documentation.
686 int rotation = 0;
687 if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
688 CameraInfo info = CameraHolder.instance().getCameraInfo()[cameraId];
689 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
690 rotation = (info.orientation - orientation + 360) % 360;
691 } else { // back-facing camera
692 rotation = (info.orientation + orientation) % 360;
693 }
694 }
695 return rotation;
696 }
697
Sascha Haeberling37f36112013-08-06 14:31:52 -0700698 /**
699 * Down-samples a jpeg byte array.
700 * @param data a byte array of jpeg data
701 * @param downSampleFactor down-sample factor
702 * @return decoded and down-sampled bitmap
703 */
704 public static Bitmap downSample(final byte[] data, int downSampleFactor) {
705 final BitmapFactory.Options opts = new BitmapFactory.Options();
706 // Downsample the image
707 opts.inSampleSize = downSampleFactor;
708 return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
709 }
710
Michael Kolb8872c232013-01-29 10:33:22 -0800711 public static void setGpsParameters(Parameters parameters, Location loc) {
712 // Clear previous GPS location from the parameters.
713 parameters.removeGpsData();
714
715 // We always encode GpsTimeStamp
716 parameters.setGpsTimestamp(System.currentTimeMillis() / 1000);
717
718 // Set GPS location.
719 if (loc != null) {
720 double lat = loc.getLatitude();
721 double lon = loc.getLongitude();
722 boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
723
724 if (hasLatLon) {
725 Log.d(TAG, "Set gps location");
726 parameters.setGpsLatitude(lat);
727 parameters.setGpsLongitude(lon);
728 parameters.setGpsProcessingMethod(loc.getProvider().toUpperCase());
729 if (loc.hasAltitude()) {
730 parameters.setGpsAltitude(loc.getAltitude());
731 } else {
732 // for NETWORK_PROVIDER location provider, we may have
733 // no altitude information, but the driver needs it, so
734 // we fake one.
735 parameters.setGpsAltitude(0);
736 }
737 if (loc.getTime() != 0) {
738 // Location.getTime() is UTC in milliseconds.
739 // gps-timestamp is UTC in seconds.
740 long utcTimeSeconds = loc.getTime() / 1000;
741 parameters.setGpsTimestamp(utcTimeSeconds);
742 }
743 } else {
744 loc = null;
745 }
746 }
747 }
748
Angus Kongd82eae22013-06-11 12:00:15 -0700749
750 public static int[] getMaxPreviewFpsRange(Parameters params) {
751 List<int[]> frameRates = params.getSupportedPreviewFpsRange();
752 if (frameRates != null && frameRates.size() > 0) {
753 // The list is sorted. Return the last element.
754 return frameRates.get(frameRates.size() - 1);
755 }
756 return new int[0];
757 }
758
Michael Kolb8872c232013-01-29 10:33:22 -0800759 private static class ImageFileNamer {
760 private SimpleDateFormat mFormat;
761
762 // The date (in milliseconds) used to generate the last name.
763 private long mLastDate;
764
765 // Number of names generated for the same second.
766 private int mSameSecondCount;
767
768 public ImageFileNamer(String format) {
769 mFormat = new SimpleDateFormat(format);
770 }
771
772 public String generateName(long dateTaken) {
773 Date date = new Date(dateTaken);
774 String result = mFormat.format(date);
775
776 // If the last name was generated for the same second,
777 // we append _1, _2, etc to the name.
778 if (dateTaken / 1000 == mLastDate / 1000) {
779 mSameSecondCount++;
780 result += "_" + mSameSecondCount;
781 } else {
782 mLastDate = dateTaken;
783 mSameSecondCount = 0;
784 }
785
786 return result;
787 }
788 }
Angus Kongb40738a2013-06-03 14:55:34 -0700789
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700790 public static void playVideo(Activity activity, Uri uri, String title) {
Angus Kongb40738a2013-06-03 14:55:34 -0700791 try {
792 Intent intent = new Intent(Intent.ACTION_VIEW)
793 .setDataAndType(uri, "video/*")
794 .putExtra(Intent.EXTRA_TITLE, title)
Sascha Haeberling8e963a52013-08-06 11:43:02 -0700795 .putExtra(KEY_TREAT_UP_AS_BACK, true);
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700796 activity.startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
Angus Kongb40738a2013-06-03 14:55:34 -0700797 } catch (ActivityNotFoundException e) {
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700798 Toast.makeText(activity, activity.getString(R.string.video_err),
Angus Kongb40738a2013-06-03 14:55:34 -0700799 Toast.LENGTH_SHORT).show();
800 }
801 }
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700802
803 /**
804 * Starts GMM with the given location shown. If this fails, and GMM could
805 * not be found, we use a geo intent as a fallback.
806 *
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700807 * @param activity the activity to use for launching the Maps intent.
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700808 * @param latLong a 2-element array containing {latitude/longitude}.
809 */
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700810 public static void showOnMap(Activity activity, double[] latLong) {
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700811 try {
812 // We don't use "geo:latitude,longitude" because it only centers
813 // the MapView to the specified location, but we need a marker
814 // for further operations (routing to/from).
815 // The q=(lat, lng) syntax is suggested by geo-team.
816 String uri = String.format(Locale.ENGLISH, "http://maps.google.com/maps?f=q&q=(%f,%f)",
817 latLong[0], latLong[1]);
818 ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME,
819 MAPS_CLASS_NAME);
820 Intent mapsIntent = new Intent(Intent.ACTION_VIEW,
821 Uri.parse(uri)).setComponent(compName);
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700822 activity.startActivityForResult(mapsIntent,
823 CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700824 } catch (ActivityNotFoundException e) {
825 // Use the "geo intent" if no GMM is installed
826 Log.e(TAG, "GMM activity not found!", e);
827 String url = String.format(Locale.ENGLISH, "geo:%f,%f", latLong[0], latLong[1]);
828 Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
Sascha Haeberlingb7639c62013-09-09 14:42:43 -0700829 activity.startActivity(mapsIntent);
Sascha Haeberlingfae11a12013-08-15 14:29:49 -0700830 }
831 }
Michael Kolb8872c232013-01-29 10:33:22 -0800832}