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