1/* 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.provider.Settings; 43import android.telephony.TelephonyManager; 44import android.util.DisplayMetrics; 45import android.util.FloatMath; 46import android.util.Log; 47import android.util.TypedValue; 48import android.view.Display; 49import android.view.OrientationEventListener; 50import android.view.Surface; 51import android.view.View; 52import android.view.WindowManager; 53import android.view.animation.AlphaAnimation; 54import android.view.animation.Animation; 55 56import com.android.gallery3d.R; 57import com.android.gallery3d.common.ApiHelper; 58 59import java.io.Closeable; 60import java.io.IOException; 61import java.lang.reflect.Method; 62import java.text.SimpleDateFormat; 63import java.util.Date; 64import java.util.List; 65import java.util.StringTokenizer; 66 67/** 68 * Collection of utility functions used in this package. 69 */ 70public class Util { 71 private static final String TAG = "Util"; 72 73 // Orientation hysteresis amount used in rounding, in degrees 74 public static final int ORIENTATION_HYSTERESIS = 5; 75 76 public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW"; 77 // See android.hardware.Camera.ACTION_NEW_PICTURE. 78 public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE"; 79 // See android.hardware.Camera.ACTION_NEW_VIDEO. 80 public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO"; 81 82 // Fields from android.hardware.Camera.Parameters 83 public static final String FOCUS_MODE_CONTINUOUS_PICTURE = "continuous-picture"; 84 public static final String RECORDING_HINT = "recording-hint"; 85 private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported"; 86 private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported"; 87 private static final String VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported"; 88 public static final String SCENE_MODE_HDR = "hdr"; 89 public static final String TRUE = "true"; 90 public static final String FALSE = "false"; 91 92 public static boolean isSupported(String value, List<String> supported) { 93 return supported == null ? false : supported.indexOf(value) >= 0; 94 } 95 96 public static boolean isAutoExposureLockSupported(Parameters params) { 97 return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED)); 98 } 99 100 public static boolean isAutoWhiteBalanceLockSupported(Parameters params) { 101 return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED)); 102 } 103 104 public static boolean isVideoSnapshotSupported(Parameters params) { 105 return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED)); 106 } 107 108 public static boolean isCameraHdrSupported(Parameters params) { 109 List<String> supported = params.getSupportedSceneModes(); 110 return (supported != null) && supported.contains(SCENE_MODE_HDR); 111 } 112 113 @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) 114 public static boolean isMeteringAreaSupported(Parameters params) { 115 if (ApiHelper.HAS_CAMERA_METERING_AREA) { 116 return params.getMaxNumMeteringAreas() > 0; 117 } 118 return false; 119 } 120 121 @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) 122 public static boolean isFocusAreaSupported(Parameters params) { 123 if (ApiHelper.HAS_CAMERA_FOCUS_AREA) { 124 return (params.getMaxNumFocusAreas() > 0 125 && isSupported(Parameters.FOCUS_MODE_AUTO, 126 params.getSupportedFocusModes())); 127 } 128 return false; 129 } 130 131 // Private intent extras. Test only. 132 private static final String EXTRAS_CAMERA_FACING = 133 "android.intent.extras.CAMERA_FACING"; 134 135 private static float sPixelDensity = 1; 136 private static ImageFileNamer sImageFileNamer; 137 138 private Util() { 139 } 140 141 public static void initialize(Context context) { 142 DisplayMetrics metrics = new DisplayMetrics(); 143 WindowManager wm = (WindowManager) 144 context.getSystemService(Context.WINDOW_SERVICE); 145 wm.getDefaultDisplay().getMetrics(metrics); 146 sPixelDensity = metrics.density; 147 sImageFileNamer = new ImageFileNamer( 148 context.getString(R.string.image_file_name_format)); 149 } 150 151 public static int dpToPixel(int dp) { 152 return Math.round(sPixelDensity * dp); 153 } 154 155 // Rotates the bitmap by the specified degree. 156 // If a new bitmap is created, the original bitmap is recycled. 157 public static Bitmap rotate(Bitmap b, int degrees) { 158 return rotateAndMirror(b, degrees, false); 159 } 160 161 // Rotates and/or mirrors the bitmap. If a new bitmap is created, the 162 // original bitmap is recycled. 163 public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) { 164 if ((degrees != 0 || mirror) && b != null) { 165 Matrix m = new Matrix(); 166 // Mirror first. 167 // horizontal flip + rotation = -rotation + horizontal flip 168 if (mirror) { 169 m.postScale(-1, 1); 170 degrees = (degrees + 360) % 360; 171 if (degrees == 0 || degrees == 180) { 172 m.postTranslate(b.getWidth(), 0); 173 } else if (degrees == 90 || degrees == 270) { 174 m.postTranslate(b.getHeight(), 0); 175 } else { 176 throw new IllegalArgumentException("Invalid degrees=" + degrees); 177 } 178 } 179 if (degrees != 0) { 180 // clockwise 181 m.postRotate(degrees, 182 (float) b.getWidth() / 2, (float) b.getHeight() / 2); 183 } 184 185 try { 186 Bitmap b2 = Bitmap.createBitmap( 187 b, 0, 0, b.getWidth(), b.getHeight(), m, true); 188 if (b != b2) { 189 b.recycle(); 190 b = b2; 191 } 192 } catch (OutOfMemoryError ex) { 193 // We have no memory to rotate. Return the original bitmap. 194 } 195 } 196 return b; 197 } 198 199 /* 200 * Compute the sample size as a function of minSideLength 201 * and maxNumOfPixels. 202 * minSideLength is used to specify that minimal width or height of a 203 * bitmap. 204 * maxNumOfPixels is used to specify the maximal size in pixels that is 205 * tolerable in terms of memory usage. 206 * 207 * The function returns a sample size based on the constraints. 208 * Both size and minSideLength can be passed in as -1 209 * which indicates no care of the corresponding constraint. 210 * The functions prefers returning a sample size that 211 * generates a smaller bitmap, unless minSideLength = -1. 212 * 213 * Also, the function rounds up the sample size to a power of 2 or multiple 214 * of 8 because BitmapFactory only honors sample size this way. 215 * For example, BitmapFactory downsamples an image by 2 even though the 216 * request is 3. So we round up the sample size to avoid OOM. 217 */ 218 public static int computeSampleSize(BitmapFactory.Options options, 219 int minSideLength, int maxNumOfPixels) { 220 int initialSize = computeInitialSampleSize(options, minSideLength, 221 maxNumOfPixels); 222 223 int roundedSize; 224 if (initialSize <= 8) { 225 roundedSize = 1; 226 while (roundedSize < initialSize) { 227 roundedSize <<= 1; 228 } 229 } else { 230 roundedSize = (initialSize + 7) / 8 * 8; 231 } 232 233 return roundedSize; 234 } 235 236 private static int computeInitialSampleSize(BitmapFactory.Options options, 237 int minSideLength, int maxNumOfPixels) { 238 double w = options.outWidth; 239 double h = options.outHeight; 240 241 int lowerBound = (maxNumOfPixels < 0) ? 1 : 242 (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels)); 243 int upperBound = (minSideLength < 0) ? 128 : 244 (int) Math.min(Math.floor(w / minSideLength), 245 Math.floor(h / minSideLength)); 246 247 if (upperBound < lowerBound) { 248 // return the larger one when there is no overlapping zone. 249 return lowerBound; 250 } 251 252 if (maxNumOfPixels < 0 && minSideLength < 0) { 253 return 1; 254 } else if (minSideLength < 0) { 255 return lowerBound; 256 } else { 257 return upperBound; 258 } 259 } 260 261 public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) { 262 try { 263 BitmapFactory.Options options = new BitmapFactory.Options(); 264 options.inJustDecodeBounds = true; 265 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, 266 options); 267 if (options.mCancel || options.outWidth == -1 268 || options.outHeight == -1) { 269 return null; 270 } 271 options.inSampleSize = computeSampleSize( 272 options, -1, maxNumOfPixels); 273 options.inJustDecodeBounds = false; 274 275 options.inDither = false; 276 options.inPreferredConfig = Bitmap.Config.ARGB_8888; 277 return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, 278 options); 279 } catch (OutOfMemoryError ex) { 280 Log.e(TAG, "Got oom exception ", ex); 281 return null; 282 } 283 } 284 285 public static void closeSilently(Closeable c) { 286 if (c == null) return; 287 try { 288 c.close(); 289 } catch (Throwable t) { 290 // do nothing 291 } 292 } 293 294 public static void Assert(boolean cond) { 295 if (!cond) { 296 throw new AssertionError(); 297 } 298 } 299 300 @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) 301 private static void throwIfCameraDisabled(Activity activity) throws CameraDisabledException { 302 // Check if device policy has disabled the camera. 303 if (ApiHelper.HAS_GET_CAMERA_DISABLED) { 304 DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService( 305 Context.DEVICE_POLICY_SERVICE); 306 if (dpm.getCameraDisabled(null)) { 307 throw new CameraDisabledException(); 308 } 309 } 310 } 311 312 public static CameraManager.CameraProxy openCamera(Activity activity, int cameraId) 313 throws CameraHardwareException, CameraDisabledException { 314 throwIfCameraDisabled(activity); 315 316 try { 317 return CameraHolder.instance().open(cameraId); 318 } catch (CameraHardwareException e) { 319 // In eng build, we throw the exception so that test tool 320 // can detect it and report it 321 if ("eng".equals(Build.TYPE)) { 322 throw new RuntimeException("openCamera failed", e); 323 } else { 324 throw e; 325 } 326 } 327 } 328 329 public static void showErrorAndFinish(final Activity activity, int msgId) { 330 DialogInterface.OnClickListener buttonListener = 331 new DialogInterface.OnClickListener() { 332 @Override 333 public void onClick(DialogInterface dialog, int which) { 334 activity.finish(); 335 } 336 }; 337 TypedValue out = new TypedValue(); 338 activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true); 339 new AlertDialog.Builder(activity) 340 .setCancelable(false) 341 .setTitle(R.string.camera_error_title) 342 .setMessage(msgId) 343 .setNeutralButton(R.string.dialog_ok, buttonListener) 344 .setIcon(out.resourceId) 345 .show(); 346 } 347 348 public static <T> T checkNotNull(T object) { 349 if (object == null) throw new NullPointerException(); 350 return object; 351 } 352 353 public static boolean equals(Object a, Object b) { 354 return (a == b) || (a == null ? false : a.equals(b)); 355 } 356 357 public static int nextPowerOf2(int n) { 358 n -= 1; 359 n |= n >>> 16; 360 n |= n >>> 8; 361 n |= n >>> 4; 362 n |= n >>> 2; 363 n |= n >>> 1; 364 return n + 1; 365 } 366 367 public static float distance(float x, float y, float sx, float sy) { 368 float dx = x - sx; 369 float dy = y - sy; 370 return FloatMath.sqrt(dx * dx + dy * dy); 371 } 372 373 public static int clamp(int x, int min, int max) { 374 if (x > max) return max; 375 if (x < min) return min; 376 return x; 377 } 378 379 public static boolean systemRotationLocked(Activity activity) { 380 return Settings.System.getInt(activity.getContentResolver(), 381 Settings.System.ACCELEROMETER_ROTATION, 0) == 0; 382 } 383 384 public static int getDisplayRotation(Activity activity) { 385 int rotation = activity.getWindowManager().getDefaultDisplay() 386 .getRotation(); 387 switch (rotation) { 388 case Surface.ROTATION_0: return 0; 389 case Surface.ROTATION_90: return 90; 390 case Surface.ROTATION_180: return 180; 391 case Surface.ROTATION_270: return 270; 392 } 393 return 0; 394 } 395 396 public static int getDisplayOrientation(int degrees, int cameraId) { 397 // See android.hardware.Camera.setDisplayOrientation for 398 // documentation. 399 Camera.CameraInfo info = new Camera.CameraInfo(); 400 Camera.getCameraInfo(cameraId, info); 401 int result; 402 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 403 result = (info.orientation + degrees) % 360; 404 result = (360 - result) % 360; // compensate the mirror 405 } else { // back-facing 406 result = (info.orientation - degrees + 360) % 360; 407 } 408 return result; 409 } 410 411 public static int getCameraOrientation(int cameraId) { 412 Camera.CameraInfo info = new Camera.CameraInfo(); 413 Camera.getCameraInfo(cameraId, info); 414 return info.orientation; 415 } 416 417 public static int roundOrientation(int orientation, int orientationHistory) { 418 boolean changeOrientation = false; 419 if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) { 420 changeOrientation = true; 421 } else { 422 int dist = Math.abs(orientation - orientationHistory); 423 dist = Math.min( dist, 360 - dist ); 424 changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS ); 425 } 426 if (changeOrientation) { 427 return ((orientation + 45) / 90 * 90) % 360; 428 } 429 return orientationHistory; 430 } 431 432 @SuppressWarnings("deprecation") 433 @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) 434 private static Point getDefaultDisplaySize(Activity activity, Point size) { 435 Display d = activity.getWindowManager().getDefaultDisplay(); 436 if (Build.VERSION.SDK_INT >= ApiHelper.VERSION_CODES.HONEYCOMB_MR2) { 437 d.getSize(size); 438 } else { 439 size.set(d.getWidth(), d.getHeight()); 440 } 441 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 = 558 currentActivity.getIntent().getIntExtra(Util.EXTRAS_CAMERA_FACING, -1); 559 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 619 public static void viewUri(Uri uri, Context context) { 620 if (!isUriValid(uri, context.getContentResolver())) { 621 Log.e(TAG, "Uri invalid. uri=" + uri); 622 return; 623 } 624 625 try { 626 context.startActivity(new Intent(Util.REVIEW_ACTION, uri)); 627 } catch (ActivityNotFoundException ex) { 628 try { 629 context.startActivity(new Intent(Intent.ACTION_VIEW, uri)); 630 } catch (ActivityNotFoundException e) { 631 Log.e(TAG, "review image fail. uri=" + uri, e); 632 } 633 } 634 } 635 636 public static void dumpRect(RectF rect, String msg) { 637 Log.v(TAG, msg + "=(" + rect.left + "," + rect.top 638 + "," + rect.right + "," + rect.bottom + ")"); 639 } 640 641 public static void rectFToRect(RectF rectF, Rect rect) { 642 rect.left = Math.round(rectF.left); 643 rect.top = Math.round(rectF.top); 644 rect.right = Math.round(rectF.right); 645 rect.bottom = Math.round(rectF.bottom); 646 } 647 648 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation, 649 int viewWidth, int viewHeight) { 650 // Need mirror for front camera. 651 matrix.setScale(mirror ? -1 : 1, 1); 652 // This is the value for android.hardware.Camera.setDisplayOrientation. 653 matrix.postRotate(displayOrientation); 654 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000). 655 // UI coordinates range from (0, 0) to (width, height). 656 matrix.postScale(viewWidth / 2000f, viewHeight / 2000f); 657 matrix.postTranslate(viewWidth / 2f, viewHeight / 2f); 658 } 659 660 public static String createJpegName(long dateTaken) { 661 synchronized (sImageFileNamer) { 662 return sImageFileNamer.generateName(dateTaken); 663 } 664 } 665 666 public static void broadcastNewPicture(Context context, Uri uri) { 667 context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri)); 668 // Keep compatibility 669 context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri)); 670 } 671 672 public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) { 673 if (view.getVisibility() == View.VISIBLE) return; 674 675 view.setVisibility(View.VISIBLE); 676 Animation animation = new AlphaAnimation(startAlpha, endAlpha); 677 animation.setDuration(duration); 678 view.startAnimation(animation); 679 } 680 681 public static void fadeIn(View view) { 682 fadeIn(view, 0F, 1F, 400); 683 684 // We disabled the button in fadeOut(), so enable it here. 685 view.setEnabled(true); 686 } 687 688 public static void fadeOut(View view) { 689 if (view.getVisibility() != View.VISIBLE) return; 690 691 // Since the button is still clickable before fade-out animation 692 // ends, we disable the button first to block click. 693 view.setEnabled(false); 694 Animation animation = new AlphaAnimation(1F, 0F); 695 animation.setDuration(400); 696 view.startAnimation(animation); 697 view.setVisibility(View.GONE); 698 } 699 700 public static int getJpegRotation(int cameraId, int orientation) { 701 // See android.hardware.Camera.Parameters.setRotation for 702 // documentation. 703 int rotation = 0; 704 if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) { 705 CameraInfo info = CameraHolder.instance().getCameraInfo()[cameraId]; 706 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) { 707 rotation = (info.orientation - orientation + 360) % 360; 708 } else { // back-facing camera 709 rotation = (info.orientation + orientation) % 360; 710 } 711 } 712 return rotation; 713 } 714 715 public static void setGpsParameters(Parameters parameters, Location loc) { 716 // Clear previous GPS location from the parameters. 717 parameters.removeGpsData(); 718 719 // We always encode GpsTimeStamp 720 parameters.setGpsTimestamp(System.currentTimeMillis() / 1000); 721 722 // Set GPS location. 723 if (loc != null) { 724 double lat = loc.getLatitude(); 725 double lon = loc.getLongitude(); 726 boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d); 727 728 if (hasLatLon) { 729 Log.d(TAG, "Set gps location"); 730 parameters.setGpsLatitude(lat); 731 parameters.setGpsLongitude(lon); 732 parameters.setGpsProcessingMethod(loc.getProvider().toUpperCase()); 733 if (loc.hasAltitude()) { 734 parameters.setGpsAltitude(loc.getAltitude()); 735 } else { 736 // for NETWORK_PROVIDER location provider, we may have 737 // no altitude information, but the driver needs it, so 738 // we fake one. 739 parameters.setGpsAltitude(0); 740 } 741 if (loc.getTime() != 0) { 742 // Location.getTime() is UTC in milliseconds. 743 // gps-timestamp is UTC in seconds. 744 long utcTimeSeconds = loc.getTime() / 1000; 745 parameters.setGpsTimestamp(utcTimeSeconds); 746 } 747 } else { 748 loc = null; 749 } 750 } 751 } 752 753 private static class ImageFileNamer { 754 private SimpleDateFormat mFormat; 755 756 // The date (in milliseconds) used to generate the last name. 757 private long mLastDate; 758 759 // Number of names generated for the same second. 760 private int mSameSecondCount; 761 762 public ImageFileNamer(String format) { 763 mFormat = new SimpleDateFormat(format); 764 } 765 766 public String generateName(long dateTaken) { 767 Date date = new Date(dateTaken); 768 String result = mFormat.format(date); 769 770 // If the last name was generated for the same second, 771 // we append _1, _2, etc to the name. 772 if (dateTaken / 1000 == mLastDate / 1000) { 773 mSameSecondCount++; 774 result += "_" + mSameSecondCount; 775 } else { 776 mLastDate = dateTaken; 777 mSameSecondCount = 0; 778 } 779 780 return result; 781 } 782 } 783} 784