blob: 6c3b3640be2d4903430fdd2b16cdf483a1775337 [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
17package com.android.camera;
18
19import android.annotation.TargetApi;
20import android.app.Activity;
21import android.app.AlertDialog;
22import android.app.admin.DevicePolicyManager;
23import android.content.ActivityNotFoundException;
24import 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;
40import android.os.Build;
41import android.os.ParcelFileDescriptor;
42import android.telephony.TelephonyManager;
43import android.util.DisplayMetrics;
44import android.util.FloatMath;
45import android.util.Log;
46import android.util.TypedValue;
47import android.view.Display;
48import android.view.OrientationEventListener;
49import android.view.Surface;
50import android.view.View;
51import android.view.WindowManager;
52import android.view.animation.AlphaAnimation;
53import android.view.animation.Animation;
Angus Kongb40738a2013-06-03 14:55:34 -070054import android.widget.Toast;
Michael Kolb8872c232013-01-29 10:33:22 -080055
John Reck54987e82013-02-15 15:51:30 -080056import com.android.gallery3d.R;
Angus Kongb40738a2013-06-03 14:55:34 -070057import com.android.gallery3d.app.MovieActivity;
Michael Kolb8872c232013-01-29 10:33:22 -080058import com.android.gallery3d.common.ApiHelper;
59
60import 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.StringTokenizer;
67
68/**
69 * Collection of utility functions used in this package.
70 */
71public class Util {
72 private static final String TAG = "Util";
73
74 // Orientation hysteresis amount used in rounding, in degrees
75 public static final int ORIENTATION_HYSTERESIS = 5;
76
77 public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW";
78 // See android.hardware.Camera.ACTION_NEW_PICTURE.
79 public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
80 // See android.hardware.Camera.ACTION_NEW_VIDEO.
81 public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
82
83 // Fields from android.hardware.Camera.Parameters
84 public static final String FOCUS_MODE_CONTINUOUS_PICTURE = "continuous-picture";
85 public static final String RECORDING_HINT = "recording-hint";
86 private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
87 private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported";
88 private static final String VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported";
89 public static final String SCENE_MODE_HDR = "hdr";
90 public static final String TRUE = "true";
91 public static final String FALSE = "false";
92
93 public static boolean isSupported(String value, List<String> supported) {
94 return supported == null ? false : supported.indexOf(value) >= 0;
95 }
96
97 public static boolean isAutoExposureLockSupported(Parameters params) {
98 return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
99 }
100
101 public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
102 return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
103 }
104
105 public static boolean isVideoSnapshotSupported(Parameters params) {
106 return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED));
107 }
108
109 public static boolean isCameraHdrSupported(Parameters params) {
110 List<String> supported = params.getSupportedSceneModes();
111 return (supported != null) && supported.contains(SCENE_MODE_HDR);
112 }
113
114 @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
115 public static boolean isMeteringAreaSupported(Parameters params) {
116 if (ApiHelper.HAS_CAMERA_METERING_AREA) {
117 return params.getMaxNumMeteringAreas() > 0;
118 }
119 return false;
120 }
121
122 @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
123 public static boolean isFocusAreaSupported(Parameters params) {
124 if (ApiHelper.HAS_CAMERA_FOCUS_AREA) {
125 return (params.getMaxNumFocusAreas() > 0
126 && isSupported(Parameters.FOCUS_MODE_AUTO,
127 params.getSupportedFocusModes()));
128 }
129 return false;
130 }
131
132 // Private intent extras. Test only.
133 private static final String EXTRAS_CAMERA_FACING =
134 "android.intent.extras.CAMERA_FACING";
135
136 private static float sPixelDensity = 1;
137 private static ImageFileNamer sImageFileNamer;
138
139 private Util() {
140 }
141
142 public static void initialize(Context context) {
143 DisplayMetrics metrics = new DisplayMetrics();
144 WindowManager wm = (WindowManager)
145 context.getSystemService(Context.WINDOW_SERVICE);
146 wm.getDefaultDisplay().getMetrics(metrics);
147 sPixelDensity = metrics.density;
148 sImageFileNamer = new ImageFileNamer(
149 context.getString(R.string.image_file_name_format));
150 }
151
152 public static int dpToPixel(int dp) {
153 return Math.round(sPixelDensity * dp);
154 }
155
156 // Rotates the bitmap by the specified degree.
157 // If a new bitmap is created, the original bitmap is recycled.
158 public static Bitmap rotate(Bitmap b, int degrees) {
159 return rotateAndMirror(b, degrees, false);
160 }
161
162 // Rotates and/or mirrors the bitmap. If a new bitmap is created, the
163 // original bitmap is recycled.
164 public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) {
165 if ((degrees != 0 || mirror) && b != null) {
166 Matrix m = new Matrix();
167 // Mirror first.
168 // horizontal flip + rotation = -rotation + horizontal flip
169 if (mirror) {
170 m.postScale(-1, 1);
171 degrees = (degrees + 360) % 360;
172 if (degrees == 0 || degrees == 180) {
173 m.postTranslate(b.getWidth(), 0);
174 } else if (degrees == 90 || degrees == 270) {
175 m.postTranslate(b.getHeight(), 0);
176 } else {
177 throw new IllegalArgumentException("Invalid degrees=" + degrees);
178 }
179 }
180 if (degrees != 0) {
181 // clockwise
182 m.postRotate(degrees,
183 (float) b.getWidth() / 2, (float) b.getHeight() / 2);
184 }
185
186 try {
187 Bitmap b2 = Bitmap.createBitmap(
188 b, 0, 0, b.getWidth(), b.getHeight(), m, true);
189 if (b != b2) {
190 b.recycle();
191 b = b2;
192 }
193 } catch (OutOfMemoryError ex) {
194 // We have no memory to rotate. Return the original bitmap.
195 }
196 }
197 return b;
198 }
199
200 /*
201 * Compute the sample size as a function of minSideLength
202 * and maxNumOfPixels.
203 * minSideLength is used to specify that minimal width or height of a
204 * bitmap.
205 * maxNumOfPixels is used to specify the maximal size in pixels that is
206 * tolerable in terms of memory usage.
207 *
208 * The function returns a sample size based on the constraints.
209 * Both size and minSideLength can be passed in as -1
210 * which indicates no care of the corresponding constraint.
211 * The functions prefers returning a sample size that
212 * generates a smaller bitmap, unless minSideLength = -1.
213 *
214 * Also, the function rounds up the sample size to a power of 2 or multiple
215 * of 8 because BitmapFactory only honors sample size this way.
216 * For example, BitmapFactory downsamples an image by 2 even though the
217 * request is 3. So we round up the sample size to avoid OOM.
218 */
219 public static int computeSampleSize(BitmapFactory.Options options,
220 int minSideLength, int maxNumOfPixels) {
221 int initialSize = computeInitialSampleSize(options, minSideLength,
222 maxNumOfPixels);
223
224 int roundedSize;
225 if (initialSize <= 8) {
226 roundedSize = 1;
227 while (roundedSize < initialSize) {
228 roundedSize <<= 1;
229 }
230 } else {
231 roundedSize = (initialSize + 7) / 8 * 8;
232 }
233
234 return roundedSize;
235 }
236
237 private static int computeInitialSampleSize(BitmapFactory.Options options,
238 int minSideLength, int maxNumOfPixels) {
239 double w = options.outWidth;
240 double h = options.outHeight;
241
242 int lowerBound = (maxNumOfPixels < 0) ? 1 :
243 (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
244 int upperBound = (minSideLength < 0) ? 128 :
245 (int) Math.min(Math.floor(w / minSideLength),
246 Math.floor(h / minSideLength));
247
248 if (upperBound < lowerBound) {
249 // return the larger one when there is no overlapping zone.
250 return lowerBound;
251 }
252
253 if (maxNumOfPixels < 0 && minSideLength < 0) {
254 return 1;
255 } else if (minSideLength < 0) {
256 return lowerBound;
257 } else {
258 return upperBound;
259 }
260 }
261
262 public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) {
263 try {
264 BitmapFactory.Options options = new BitmapFactory.Options();
265 options.inJustDecodeBounds = true;
266 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
267 options);
268 if (options.mCancel || options.outWidth == -1
269 || options.outHeight == -1) {
270 return null;
271 }
272 options.inSampleSize = computeSampleSize(
273 options, -1, maxNumOfPixels);
274 options.inJustDecodeBounds = false;
275
276 options.inDither = false;
277 options.inPreferredConfig = Bitmap.Config.ARGB_8888;
278 return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
279 options);
280 } catch (OutOfMemoryError ex) {
281 Log.e(TAG, "Got oom exception ", ex);
282 return null;
283 }
284 }
285
286 public static void closeSilently(Closeable c) {
287 if (c == null) return;
288 try {
289 c.close();
290 } catch (Throwable t) {
291 // do nothing
292 }
293 }
294
295 public static void Assert(boolean cond) {
296 if (!cond) {
297 throw new AssertionError();
298 }
299 }
300
301 @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
302 private static void throwIfCameraDisabled(Activity activity) throws CameraDisabledException {
303 // Check if device policy has disabled the camera.
304 if (ApiHelper.HAS_GET_CAMERA_DISABLED) {
305 DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService(
306 Context.DEVICE_POLICY_SERVICE);
307 if (dpm.getCameraDisabled(null)) {
308 throw new CameraDisabledException();
309 }
310 }
311 }
312
313 public static CameraManager.CameraProxy openCamera(Activity activity, int cameraId)
314 throws CameraHardwareException, CameraDisabledException {
315 throwIfCameraDisabled(activity);
316
317 try {
318 return CameraHolder.instance().open(cameraId);
319 } catch (CameraHardwareException e) {
320 // In eng build, we throw the exception so that test tool
321 // can detect it and report it
322 if ("eng".equals(Build.TYPE)) {
323 throw new RuntimeException("openCamera failed", e);
324 } else {
325 throw e;
326 }
327 }
328 }
329
330 public static void showErrorAndFinish(final Activity activity, int msgId) {
331 DialogInterface.OnClickListener buttonListener =
332 new DialogInterface.OnClickListener() {
333 @Override
334 public void onClick(DialogInterface dialog, int which) {
335 activity.finish();
336 }
337 };
338 TypedValue out = new TypedValue();
339 activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true);
340 new AlertDialog.Builder(activity)
341 .setCancelable(false)
342 .setTitle(R.string.camera_error_title)
343 .setMessage(msgId)
344 .setNeutralButton(R.string.dialog_ok, buttonListener)
345 .setIcon(out.resourceId)
346 .show();
347 }
348
349 public static <T> T checkNotNull(T object) {
350 if (object == null) throw new NullPointerException();
351 return object;
352 }
353
354 public static boolean equals(Object a, Object b) {
355 return (a == b) || (a == null ? false : a.equals(b));
356 }
357
358 public static int nextPowerOf2(int n) {
359 n -= 1;
360 n |= n >>> 16;
361 n |= n >>> 8;
362 n |= n >>> 4;
363 n |= n >>> 2;
364 n |= n >>> 1;
365 return n + 1;
366 }
367
368 public static float distance(float x, float y, float sx, float sy) {
369 float dx = x - sx;
370 float dy = y - sy;
371 return FloatMath.sqrt(dx * dx + dy * dy);
372 }
373
374 public static int clamp(int x, int min, int max) {
375 if (x > max) return max;
376 if (x < min) return min;
377 return x;
378 }
379
380 public static int getDisplayRotation(Activity activity) {
381 int rotation = activity.getWindowManager().getDefaultDisplay()
382 .getRotation();
383 switch (rotation) {
384 case Surface.ROTATION_0: return 0;
385 case Surface.ROTATION_90: return 90;
386 case Surface.ROTATION_180: return 180;
387 case Surface.ROTATION_270: return 270;
388 }
389 return 0;
390 }
391
392 public static int getDisplayOrientation(int degrees, int cameraId) {
393 // See android.hardware.Camera.setDisplayOrientation for
394 // documentation.
395 Camera.CameraInfo info = new Camera.CameraInfo();
396 Camera.getCameraInfo(cameraId, info);
397 int result;
398 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
399 result = (info.orientation + degrees) % 360;
400 result = (360 - result) % 360; // compensate the mirror
401 } else { // back-facing
402 result = (info.orientation - degrees + 360) % 360;
403 }
404 return result;
405 }
406
407 public static int getCameraOrientation(int cameraId) {
408 Camera.CameraInfo info = new Camera.CameraInfo();
409 Camera.getCameraInfo(cameraId, info);
410 return info.orientation;
411 }
412
413 public static int roundOrientation(int orientation, int orientationHistory) {
414 boolean changeOrientation = false;
415 if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
416 changeOrientation = true;
417 } else {
418 int dist = Math.abs(orientation - orientationHistory);
419 dist = Math.min( dist, 360 - dist );
420 changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS );
421 }
422 if (changeOrientation) {
423 return ((orientation + 45) / 90 * 90) % 360;
424 }
425 return orientationHistory;
426 }
427
428 @SuppressWarnings("deprecation")
429 @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
430 private static Point getDefaultDisplaySize(Activity activity, Point size) {
431 Display d = activity.getWindowManager().getDefaultDisplay();
432 if (Build.VERSION.SDK_INT >= ApiHelper.VERSION_CODES.HONEYCOMB_MR2) {
433 d.getSize(size);
434 } else {
435 size.set(d.getWidth(), d.getHeight());
436 }
437 return size;
438 }
439
440 public static Size getOptimalPreviewSize(Activity currentActivity,
441 List<Size> sizes, double targetRatio) {
442 // Use a very small tolerance because we want an exact match.
443 final double ASPECT_TOLERANCE = 0.001;
444 if (sizes == null) return null;
445
446 Size optimalSize = null;
447 double minDiff = Double.MAX_VALUE;
448
449 // Because of bugs of overlay and layout, we sometimes will try to
450 // layout the viewfinder in the portrait orientation and thus get the
451 // wrong size of preview surface. When we change the preview size, the
452 // new overlay will be created before the old one closed, which causes
453 // an exception. For now, just get the screen size.
454 Point point = getDefaultDisplaySize(currentActivity, new Point());
455 int targetHeight = Math.min(point.x, point.y);
456 // Try to find an size match aspect ratio and size
457 for (Size size : sizes) {
458 double ratio = (double) size.width / size.height;
459 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
460 if (Math.abs(size.height - targetHeight) < minDiff) {
461 optimalSize = size;
462 minDiff = Math.abs(size.height - targetHeight);
463 }
464 }
465 // Cannot find the one match the aspect ratio. This should not happen.
466 // Ignore the requirement.
467 if (optimalSize == null) {
468 Log.w(TAG, "No preview size match the aspect ratio");
469 minDiff = Double.MAX_VALUE;
470 for (Size size : sizes) {
471 if (Math.abs(size.height - targetHeight) < minDiff) {
472 optimalSize = size;
473 minDiff = Math.abs(size.height - targetHeight);
474 }
475 }
476 }
477 return optimalSize;
478 }
479
480 // Returns the largest picture size which matches the given aspect ratio.
481 public static Size getOptimalVideoSnapshotPictureSize(
482 List<Size> sizes, double targetRatio) {
483 // Use a very small tolerance because we want an exact match.
484 final double ASPECT_TOLERANCE = 0.001;
485 if (sizes == null) return null;
486
487 Size optimalSize = null;
488
489 // Try to find a size matches aspect ratio and has the largest width
490 for (Size size : sizes) {
491 double ratio = (double) size.width / size.height;
492 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
493 if (optimalSize == null || size.width > optimalSize.width) {
494 optimalSize = size;
495 }
496 }
497
498 // Cannot find one that matches the aspect ratio. This should not happen.
499 // Ignore the requirement.
500 if (optimalSize == null) {
501 Log.w(TAG, "No picture size match the aspect ratio");
502 for (Size size : sizes) {
503 if (optimalSize == null || size.width > optimalSize.width) {
504 optimalSize = size;
505 }
506 }
507 }
508 return optimalSize;
509 }
510
511 public static void dumpParameters(Parameters parameters) {
512 String flattened = parameters.flatten();
513 StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
514 Log.d(TAG, "Dump all camera parameters:");
515 while (tokenizer.hasMoreElements()) {
516 Log.d(TAG, tokenizer.nextToken());
517 }
518 }
519
520 /**
521 * Returns whether the device is voice-capable (meaning, it can do MMS).
522 */
523 public static boolean isMmsCapable(Context context) {
524 TelephonyManager telephonyManager = (TelephonyManager)
525 context.getSystemService(Context.TELEPHONY_SERVICE);
526 if (telephonyManager == null) {
527 return false;
528 }
529
530 try {
531 Class<?> partypes[] = new Class[0];
532 Method sIsVoiceCapable = TelephonyManager.class.getMethod(
533 "isVoiceCapable", partypes);
534
535 Object arglist[] = new Object[0];
536 Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist);
537 return (Boolean) retobj;
538 } catch (java.lang.reflect.InvocationTargetException ite) {
539 // Failure, must be another device.
540 // Assume that it is voice capable.
541 } catch (IllegalAccessException iae) {
542 // Failure, must be an other device.
543 // Assume that it is voice capable.
544 } catch (NoSuchMethodException nsme) {
545 }
546 return true;
547 }
548
549 // This is for test only. Allow the camera to launch the specific camera.
550 public static int getCameraFacingIntentExtras(Activity currentActivity) {
551 int cameraId = -1;
552
553 int intentCameraId =
554 currentActivity.getIntent().getIntExtra(Util.EXTRAS_CAMERA_FACING, -1);
555
556 if (isFrontCameraIntent(intentCameraId)) {
557 // Check if the front camera exist
558 int frontCameraId = CameraHolder.instance().getFrontCameraId();
559 if (frontCameraId != -1) {
560 cameraId = frontCameraId;
561 }
562 } else if (isBackCameraIntent(intentCameraId)) {
563 // Check if the back camera exist
564 int backCameraId = CameraHolder.instance().getBackCameraId();
565 if (backCameraId != -1) {
566 cameraId = backCameraId;
567 }
568 }
569 return cameraId;
570 }
571
572 private static boolean isFrontCameraIntent(int intentCameraId) {
573 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
574 }
575
576 private static boolean isBackCameraIntent(int intentCameraId) {
577 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
578 }
579
580 private static int sLocation[] = new int[2];
581
582 // This method is not thread-safe.
583 public static boolean pointInView(float x, float y, View v) {
584 v.getLocationInWindow(sLocation);
585 return x >= sLocation[0] && x < (sLocation[0] + v.getWidth())
586 && y >= sLocation[1] && y < (sLocation[1] + v.getHeight());
587 }
588
589 public static int[] getRelativeLocation(View reference, View view) {
590 reference.getLocationInWindow(sLocation);
591 int referenceX = sLocation[0];
592 int referenceY = sLocation[1];
593 view.getLocationInWindow(sLocation);
594 sLocation[0] -= referenceX;
595 sLocation[1] -= referenceY;
596 return sLocation;
597 }
598
599 public static boolean isUriValid(Uri uri, ContentResolver resolver) {
600 if (uri == null) return false;
601
602 try {
603 ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
604 if (pfd == null) {
605 Log.e(TAG, "Fail to open URI. URI=" + uri);
606 return false;
607 }
608 pfd.close();
609 } catch (IOException ex) {
610 return false;
611 }
612 return true;
613 }
614
615 public static void viewUri(Uri uri, Context context) {
616 if (!isUriValid(uri, context.getContentResolver())) {
617 Log.e(TAG, "Uri invalid. uri=" + uri);
618 return;
619 }
620
621 try {
622 context.startActivity(new Intent(Util.REVIEW_ACTION, uri));
623 } catch (ActivityNotFoundException ex) {
624 try {
625 context.startActivity(new Intent(Intent.ACTION_VIEW, uri));
626 } catch (ActivityNotFoundException e) {
627 Log.e(TAG, "review image fail. uri=" + uri, e);
628 }
629 }
630 }
631
632 public static void dumpRect(RectF rect, String msg) {
633 Log.v(TAG, msg + "=(" + rect.left + "," + rect.top
634 + "," + rect.right + "," + rect.bottom + ")");
635 }
636
637 public static void rectFToRect(RectF rectF, Rect rect) {
638 rect.left = Math.round(rectF.left);
639 rect.top = Math.round(rectF.top);
640 rect.right = Math.round(rectF.right);
641 rect.bottom = Math.round(rectF.bottom);
642 }
643
644 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
645 int viewWidth, int viewHeight) {
646 // Need mirror for front camera.
647 matrix.setScale(mirror ? -1 : 1, 1);
648 // This is the value for android.hardware.Camera.setDisplayOrientation.
649 matrix.postRotate(displayOrientation);
650 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
651 // UI coordinates range from (0, 0) to (width, height).
652 matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
653 matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
654 }
655
656 public static String createJpegName(long dateTaken) {
657 synchronized (sImageFileNamer) {
658 return sImageFileNamer.generateName(dateTaken);
659 }
660 }
661
662 public static void broadcastNewPicture(Context context, Uri uri) {
663 context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri));
664 // Keep compatibility
665 context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri));
666 }
667
668 public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) {
669 if (view.getVisibility() == View.VISIBLE) return;
670
671 view.setVisibility(View.VISIBLE);
672 Animation animation = new AlphaAnimation(startAlpha, endAlpha);
673 animation.setDuration(duration);
674 view.startAnimation(animation);
675 }
676
677 public static void fadeIn(View view) {
678 fadeIn(view, 0F, 1F, 400);
679
680 // We disabled the button in fadeOut(), so enable it here.
681 view.setEnabled(true);
682 }
683
684 public static void fadeOut(View view) {
685 if (view.getVisibility() != View.VISIBLE) return;
686
687 // Since the button is still clickable before fade-out animation
688 // ends, we disable the button first to block click.
689 view.setEnabled(false);
690 Animation animation = new AlphaAnimation(1F, 0F);
691 animation.setDuration(400);
692 view.startAnimation(animation);
693 view.setVisibility(View.GONE);
694 }
695
696 public static int getJpegRotation(int cameraId, int orientation) {
697 // See android.hardware.Camera.Parameters.setRotation for
698 // documentation.
699 int rotation = 0;
700 if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
701 CameraInfo info = CameraHolder.instance().getCameraInfo()[cameraId];
702 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
703 rotation = (info.orientation - orientation + 360) % 360;
704 } else { // back-facing camera
705 rotation = (info.orientation + orientation) % 360;
706 }
707 }
708 return rotation;
709 }
710
711 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
749 private static class ImageFileNamer {
750 private SimpleDateFormat mFormat;
751
752 // The date (in milliseconds) used to generate the last name.
753 private long mLastDate;
754
755 // Number of names generated for the same second.
756 private int mSameSecondCount;
757
758 public ImageFileNamer(String format) {
759 mFormat = new SimpleDateFormat(format);
760 }
761
762 public String generateName(long dateTaken) {
763 Date date = new Date(dateTaken);
764 String result = mFormat.format(date);
765
766 // If the last name was generated for the same second,
767 // we append _1, _2, etc to the name.
768 if (dateTaken / 1000 == mLastDate / 1000) {
769 mSameSecondCount++;
770 result += "_" + mSameSecondCount;
771 } else {
772 mLastDate = dateTaken;
773 mSameSecondCount = 0;
774 }
775
776 return result;
777 }
778 }
Angus Kongb40738a2013-06-03 14:55:34 -0700779
780 public static void playVideo(Context context, Uri uri, String title) {
781 try {
782 Intent intent = new Intent(Intent.ACTION_VIEW)
783 .setDataAndType(uri, "video/*")
784 .putExtra(Intent.EXTRA_TITLE, title)
785 .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true);
786 context.startActivity(intent);
787 } catch (ActivityNotFoundException e) {
788 Toast.makeText(context, context.getString(R.string.video_err),
789 Toast.LENGTH_SHORT).show();
790 }
791 }
Michael Kolb8872c232013-01-29 10:33:22 -0800792}