CameraUtil.java revision 5e09d01d80b64c81f21c57f1b99dd9dc1afed18d
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.util; 18 19import android.app.Activity; 20import android.app.AlertDialog; 21import android.app.admin.DevicePolicyManager; 22import android.content.ActivityNotFoundException; 23import android.content.ComponentName; 24import android.content.ContentResolver; 25import android.content.Context; 26import android.content.DialogInterface; 27import android.content.Intent; 28import android.content.res.TypedArray; 29import android.graphics.Bitmap; 30import android.graphics.BitmapFactory; 31import android.graphics.Matrix; 32import android.graphics.Point; 33import android.graphics.Rect; 34import android.graphics.RectF; 35import android.hardware.Camera; 36import android.hardware.Camera.CameraInfo; 37import android.hardware.Camera.Parameters; 38import android.hardware.Camera.Size; 39import android.location.Location; 40import android.net.Uri; 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; 53import android.widget.Toast; 54 55import com.android.camera.CameraActivity; 56import com.android.camera.CameraDisabledException; 57import com.android.camera2.R; 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.Locale; 66import java.util.StringTokenizer; 67 68/** 69 * Collection of utility functions used in this package. 70 */ 71public class CameraUtil { 72 private static final String TAG = "Util"; 73 74 // For calculate the best fps range for still image capture. 75 private final static int MAX_PREVIEW_FPS_TIMES_1000 = 400000; 76 private final static int PREFERRED_PREVIEW_FPS_TIMES_1000 = 30000; 77 78 // For creating crop intents. 79 public static final String KEY_RETURN_DATA = "return-data"; 80 public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked"; 81 82 // Orientation hysteresis amount used in rounding, in degrees 83 public static final int ORIENTATION_HYSTERESIS = 5; 84 85 public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW"; 86 // See android.hardware.Camera.ACTION_NEW_PICTURE. 87 public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE"; 88 // See android.hardware.Camera.ACTION_NEW_VIDEO. 89 public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO"; 90 91 // Broadcast Action: The camera application has become active in picture-taking mode. 92 public static final String ACTION_CAMERA_STARTED = "com.android.camera.action.CAMERA_STARTED"; 93 // Broadcast Action: The camera application is no longer in active picture-taking mode. 94 public static final String ACTION_CAMERA_STOPPED = "com.android.camera.action.CAMERA_STOPPED"; 95 // When the camera application is active in picture-taking mode, it listens for this intent, 96 // which upon receipt will trigger the shutter to capture a new picture, as if the user had 97 // pressed the shutter button. 98 public static final String ACTION_CAMERA_SHUTTER_CLICK = 99 "com.android.camera.action.SHUTTER_CLICK"; 100 101 // Fields from android.hardware.Camera.Parameters 102 public static final String FOCUS_MODE_CONTINUOUS_PICTURE = "continuous-picture"; 103 public static final String RECORDING_HINT = "recording-hint"; 104 private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported"; 105 private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported"; 106 private static final String VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported"; 107 public static final String SCENE_MODE_HDR = "hdr"; 108 public static final String TRUE = "true"; 109 public static final String FALSE = "false"; 110 111 // Fields for the show-on-maps-functionality 112 private static final String MAPS_PACKAGE_NAME = "com.google.android.apps.maps"; 113 private static final String MAPS_CLASS_NAME = "com.google.android.maps.MapsActivity"; 114 115 /** Has to be in sync with the receiving MovieActivity. */ 116 public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back"; 117 118 public static boolean isSupported(String value, List<String> supported) { 119 return supported == null ? false : supported.indexOf(value) >= 0; 120 } 121 122 public static boolean isAutoExposureLockSupported(Parameters params) { 123 return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED)); 124 } 125 126 public static boolean isAutoWhiteBalanceLockSupported(Parameters params) { 127 return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED)); 128 } 129 130 public static boolean isVideoSnapshotSupported(Parameters params) { 131 return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED)); 132 } 133 134 public static boolean isCameraHdrSupported(Parameters params) { 135 List<String> supported = params.getSupportedSceneModes(); 136 return (supported != null) && supported.contains(SCENE_MODE_HDR); 137 } 138 139 public static boolean isMeteringAreaSupported(Parameters params) { 140 return params.getMaxNumMeteringAreas() > 0; 141 } 142 143 public static boolean isFocusAreaSupported(Parameters params) { 144 return (params.getMaxNumFocusAreas() > 0 145 && isSupported(Parameters.FOCUS_MODE_AUTO, 146 params.getSupportedFocusModes())); 147 } 148 149 // Private intent extras. Test only. 150 private static final String EXTRAS_CAMERA_FACING = 151 "android.intent.extras.CAMERA_FACING"; 152 153 private static float sPixelDensity = 1; 154 private static ImageFileNamer sImageFileNamer; 155 156 private CameraUtil() { 157 } 158 159 public static void initialize(Context context) { 160 DisplayMetrics metrics = new DisplayMetrics(); 161 WindowManager wm = (WindowManager) 162 context.getSystemService(Context.WINDOW_SERVICE); 163 wm.getDefaultDisplay().getMetrics(metrics); 164 sPixelDensity = metrics.density; 165 sImageFileNamer = new ImageFileNamer( 166 context.getString(R.string.image_file_name_format)); 167 } 168 169 public static int dpToPixel(int dp) { 170 return Math.round(sPixelDensity * dp); 171 } 172 173 // Rotates the bitmap by the specified degree. 174 // If a new bitmap is created, the original bitmap is recycled. 175 public static Bitmap rotate(Bitmap b, int degrees) { 176 return rotateAndMirror(b, degrees, false); 177 } 178 179 // Rotates and/or mirrors the bitmap. If a new bitmap is created, the 180 // original bitmap is recycled. 181 public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) { 182 if ((degrees != 0 || mirror) && b != null) { 183 Matrix m = new Matrix(); 184 // Mirror first. 185 // horizontal flip + rotation = -rotation + horizontal flip 186 if (mirror) { 187 m.postScale(-1, 1); 188 degrees = (degrees + 360) % 360; 189 if (degrees == 0 || degrees == 180) { 190 m.postTranslate(b.getWidth(), 0); 191 } else if (degrees == 90 || degrees == 270) { 192 m.postTranslate(b.getHeight(), 0); 193 } else { 194 throw new IllegalArgumentException("Invalid degrees=" + degrees); 195 } 196 } 197 if (degrees != 0) { 198 // clockwise 199 m.postRotate(degrees, 200 (float) b.getWidth() / 2, (float) b.getHeight() / 2); 201 } 202 203 try { 204 Bitmap b2 = Bitmap.createBitmap( 205 b, 0, 0, b.getWidth(), b.getHeight(), m, true); 206 if (b != b2) { 207 b.recycle(); 208 b = b2; 209 } 210 } catch (OutOfMemoryError ex) { 211 // We have no memory to rotate. Return the original bitmap. 212 } 213 } 214 return b; 215 } 216 217 /* 218 * Compute the sample size as a function of minSideLength 219 * and maxNumOfPixels. 220 * minSideLength is used to specify that minimal width or height of a 221 * bitmap. 222 * maxNumOfPixels is used to specify the maximal size in pixels that is 223 * tolerable in terms of memory usage. 224 * 225 * The function returns a sample size based on the constraints. 226 * Both size and minSideLength can be passed in as -1 227 * which indicates no care of the corresponding constraint. 228 * The functions prefers returning a sample size that 229 * generates a smaller bitmap, unless minSideLength = -1. 230 * 231 * Also, the function rounds up the sample size to a power of 2 or multiple 232 * of 8 because BitmapFactory only honors sample size this way. 233 * For example, BitmapFactory downsamples an image by 2 even though the 234 * request is 3. So we round up the sample size to avoid OOM. 235 */ 236 public static int computeSampleSize(BitmapFactory.Options options, 237 int minSideLength, int maxNumOfPixels) { 238 int initialSize = computeInitialSampleSize(options, minSideLength, 239 maxNumOfPixels); 240 241 int roundedSize; 242 if (initialSize <= 8) { 243 roundedSize = 1; 244 while (roundedSize < initialSize) { 245 roundedSize <<= 1; 246 } 247 } else { 248 roundedSize = (initialSize + 7) / 8 * 8; 249 } 250 251 return roundedSize; 252 } 253 254 private static int computeInitialSampleSize(BitmapFactory.Options options, 255 int minSideLength, int maxNumOfPixels) { 256 double w = options.outWidth; 257 double h = options.outHeight; 258 259 int lowerBound = (maxNumOfPixels < 0) ? 1 : 260 (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels)); 261 int upperBound = (minSideLength < 0) ? 128 : 262 (int) Math.min(Math.floor(w / minSideLength), 263 Math.floor(h / minSideLength)); 264 265 if (upperBound < lowerBound) { 266 // return the larger one when there is no overlapping zone. 267 return lowerBound; 268 } 269 270 if (maxNumOfPixels < 0 && minSideLength < 0) { 271 return 1; 272 } else if (minSideLength < 0) { 273 return lowerBound; 274 } else { 275 return upperBound; 276 } 277 } 278 279 public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) { 280 try { 281 BitmapFactory.Options options = new BitmapFactory.Options(); 282 options.inJustDecodeBounds = true; 283 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, 284 options); 285 if (options.mCancel || options.outWidth == -1 286 || options.outHeight == -1) { 287 return null; 288 } 289 options.inSampleSize = computeSampleSize( 290 options, -1, maxNumOfPixels); 291 options.inJustDecodeBounds = false; 292 293 options.inDither = false; 294 options.inPreferredConfig = Bitmap.Config.ARGB_8888; 295 return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, 296 options); 297 } catch (OutOfMemoryError ex) { 298 Log.e(TAG, "Got oom exception ", ex); 299 return null; 300 } 301 } 302 303 public static void closeSilently(Closeable c) { 304 if (c == null) return; 305 try { 306 c.close(); 307 } catch (Throwable t) { 308 // do nothing 309 } 310 } 311 312 public static void Assert(boolean cond) { 313 if (!cond) { 314 throw new AssertionError(); 315 } 316 } 317 318 private static void throwIfCameraDisabled(Activity activity) throws CameraDisabledException { 319 // Check if device policy has disabled the camera. 320 DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService( 321 Context.DEVICE_POLICY_SERVICE); 322 if (dpm.getCameraDisabled(null)) { 323 throw new CameraDisabledException(); 324 } 325 } 326 327 public static void showErrorAndFinish(final Activity activity, int msgId) { 328 DialogInterface.OnClickListener buttonListener = 329 new DialogInterface.OnClickListener() { 330 @Override 331 public void onClick(DialogInterface dialog, int which) { 332 activity.finish(); 333 } 334 }; 335 TypedValue out = new TypedValue(); 336 activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true); 337 new AlertDialog.Builder(activity) 338 .setCancelable(false) 339 .setTitle(R.string.camera_error_title) 340 .setMessage(msgId) 341 .setNeutralButton(R.string.dialog_ok, buttonListener) 342 .setIcon(out.resourceId) 343 .show(); 344 } 345 346 public static <T> T checkNotNull(T object) { 347 if (object == null) throw new NullPointerException(); 348 return object; 349 } 350 351 public static boolean equals(Object a, Object b) { 352 return (a == b) || (a == null ? false : a.equals(b)); 353 } 354 355 public static int nextPowerOf2(int n) { 356 n -= 1; 357 n |= n >>> 16; 358 n |= n >>> 8; 359 n |= n >>> 4; 360 n |= n >>> 2; 361 n |= n >>> 1; 362 return n + 1; 363 } 364 365 public static float distance(float x, float y, float sx, float sy) { 366 float dx = x - sx; 367 float dy = y - sy; 368 return (float) Math.sqrt(dx * dx + dy * dy); 369 } 370 371 public static int clamp(int x, int min, int max) { 372 if (x > max) return max; 373 if (x < min) return min; 374 return x; 375 } 376 377 public static float clamp(float x, float min, float max) { 378 if (x > max) return max; 379 if (x < min) return min; 380 return x; 381 } 382 383 public static int getDisplayRotation(Activity activity) { 384 int rotation = activity.getWindowManager().getDefaultDisplay() 385 .getRotation(); 386 switch (rotation) { 387 case Surface.ROTATION_0: return 0; 388 case Surface.ROTATION_90: return 90; 389 case Surface.ROTATION_180: return 180; 390 case Surface.ROTATION_270: return 270; 391 } 392 return 0; 393 } 394 395 /** 396 * Calculate the default orientation of the device based on the width and 397 * height of the display when rotation = 0 (i.e. natural width and height) 398 * @param activity the activity context 399 * @return whether the default orientation of the device is portrait 400 */ 401 public static boolean isDefaultToPortrait(Activity activity) { 402 Display currentDisplay = activity.getWindowManager().getDefaultDisplay(); 403 Point displaySize = new Point(); 404 currentDisplay.getSize(displaySize); 405 int orientation = currentDisplay.getRotation(); 406 int naturalWidth, naturalHeight; 407 if (orientation == Surface.ROTATION_0 || orientation == Surface.ROTATION_180) { 408 naturalWidth = displaySize.x; 409 naturalHeight = displaySize.y; 410 } else { 411 naturalWidth = displaySize.y; 412 naturalHeight = displaySize.x; 413 } 414 return naturalWidth < naturalHeight; 415 } 416 417 public static int getDisplayOrientation(int degrees, int cameraId) { 418 // See android.hardware.Camera.setDisplayOrientation for 419 // documentation. 420 Camera.CameraInfo info = new Camera.CameraInfo(); 421 Camera.getCameraInfo(cameraId, info); 422 int result; 423 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 424 result = (info.orientation + degrees) % 360; 425 result = (360 - result) % 360; // compensate the mirror 426 } else { // back-facing 427 result = (info.orientation - degrees + 360) % 360; 428 } 429 return result; 430 } 431 432 public static int getCameraOrientation(int cameraId) { 433 Camera.CameraInfo info = new Camera.CameraInfo(); 434 Camera.getCameraInfo(cameraId, info); 435 return info.orientation; 436 } 437 438 public static int roundOrientation(int orientation, int orientationHistory) { 439 boolean changeOrientation = false; 440 if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) { 441 changeOrientation = true; 442 } else { 443 int dist = Math.abs(orientation - orientationHistory); 444 dist = Math.min( dist, 360 - dist ); 445 changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS ); 446 } 447 if (changeOrientation) { 448 return ((orientation + 45) / 90 * 90) % 360; 449 } 450 return orientationHistory; 451 } 452 453 private static Point getDefaultDisplaySize(Activity activity, Point size) { 454 activity.getWindowManager().getDefaultDisplay().getSize(size); 455 return size; 456 } 457 458 public static Size getOptimalPreviewSize(Activity currentActivity, 459 List<Size> sizes, double targetRatio) { 460 461 Point[] points = new Point[sizes.size()]; 462 463 int index = 0; 464 for (Size s : sizes) { 465 points[index++] = new Point(s.width, s.height); 466 } 467 468 int optimalPickIndex = getOptimalPreviewSize(currentActivity, points, targetRatio); 469 return (optimalPickIndex == -1) ? null : sizes.get(optimalPickIndex); 470 } 471 472 public static int getOptimalPreviewSize(Activity currentActivity, 473 Point[] sizes, double targetRatio) { 474 // Use a very small tolerance because we want an exact match. 475 final double ASPECT_TOLERANCE = 0.01; 476 if (sizes == null) return -1; 477 478 int optimalSizeIndex = -1; 479 double minDiff = Double.MAX_VALUE; 480 481 // Because of bugs of overlay and layout, we sometimes will try to 482 // layout the viewfinder in the portrait orientation and thus get the 483 // wrong size of preview surface. When we change the preview size, the 484 // new overlay will be created before the old one closed, which causes 485 // an exception. For now, just get the screen size. 486 Point point = getDefaultDisplaySize(currentActivity, new Point()); 487 int targetHeight = Math.min(point.x, point.y); 488 // Try to find an size match aspect ratio and size 489 for (int i = 0; i < sizes.length; i++) { 490 Point size = sizes[i]; 491 double ratio = (double) size.x / size.y; 492 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue; 493 if (Math.abs(size.y - targetHeight) < minDiff) { 494 optimalSizeIndex = i; 495 minDiff = Math.abs(size.y - targetHeight); 496 } 497 } 498 // Cannot find the one match the aspect ratio. This should not happen. 499 // Ignore the requirement. 500 if (optimalSizeIndex == -1) { 501 Log.w(TAG, "No preview size match the aspect ratio"); 502 minDiff = Double.MAX_VALUE; 503 for (int i = 0; i < sizes.length; i++) { 504 Point size = sizes[i]; 505 if (Math.abs(size.y - targetHeight) < minDiff) { 506 optimalSizeIndex = i; 507 minDiff = Math.abs(size.y - targetHeight); 508 } 509 } 510 } 511 return optimalSizeIndex; 512 } 513 514 // Returns the largest picture size which matches the given aspect ratio. 515 public static Size getOptimalVideoSnapshotPictureSize( 516 List<Size> sizes, double targetRatio) { 517 // Use a very small tolerance because we want an exact match. 518 final double ASPECT_TOLERANCE = 0.001; 519 if (sizes == null) return null; 520 521 Size optimalSize = null; 522 523 // Try to find a size matches aspect ratio and has the largest width 524 for (Size size : sizes) { 525 double ratio = (double) size.width / size.height; 526 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue; 527 if (optimalSize == null || size.width > optimalSize.width) { 528 optimalSize = size; 529 } 530 } 531 532 // Cannot find one that matches the aspect ratio. This should not happen. 533 // Ignore the requirement. 534 if (optimalSize == null) { 535 Log.w(TAG, "No picture size match the aspect ratio"); 536 for (Size size : sizes) { 537 if (optimalSize == null || size.width > optimalSize.width) { 538 optimalSize = size; 539 } 540 } 541 } 542 return optimalSize; 543 } 544 545 public static void dumpParameters(Parameters parameters) { 546 String flattened = parameters.flatten(); 547 StringTokenizer tokenizer = new StringTokenizer(flattened, ";"); 548 Log.d(TAG, "Dump all camera parameters:"); 549 while (tokenizer.hasMoreElements()) { 550 Log.d(TAG, tokenizer.nextToken()); 551 } 552 } 553 554 /** 555 * Returns whether the device is voice-capable (meaning, it can do MMS). 556 */ 557 public static boolean isMmsCapable(Context context) { 558 TelephonyManager telephonyManager = (TelephonyManager) 559 context.getSystemService(Context.TELEPHONY_SERVICE); 560 if (telephonyManager == null) { 561 return false; 562 } 563 564 try { 565 Class<?> partypes[] = new Class[0]; 566 Method sIsVoiceCapable = TelephonyManager.class.getMethod( 567 "isVoiceCapable", partypes); 568 569 Object arglist[] = new Object[0]; 570 Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist); 571 return (Boolean) retobj; 572 } catch (java.lang.reflect.InvocationTargetException ite) { 573 // Failure, must be another device. 574 // Assume that it is voice capable. 575 } catch (IllegalAccessException iae) { 576 // Failure, must be an other device. 577 // Assume that it is voice capable. 578 } catch (NoSuchMethodException nsme) { 579 } 580 return true; 581 } 582 583 // This is for test only. Allow the camera to launch the specific camera. 584 public static int getCameraFacingIntentExtras(Activity currentActivity) { 585 int cameraId = -1; 586 587 int intentCameraId = 588 currentActivity.getIntent().getIntExtra(CameraUtil.EXTRAS_CAMERA_FACING, -1); 589 590 if (isFrontCameraIntent(intentCameraId)) { 591 // Check if the front camera exist 592 int frontCameraId = ((CameraActivity) currentActivity).getCameraProvider() 593 .getFirstFrontCameraId(); 594 if (frontCameraId != -1) { 595 cameraId = frontCameraId; 596 } 597 } else if (isBackCameraIntent(intentCameraId)) { 598 // Check if the back camera exist 599 int backCameraId = ((CameraActivity) currentActivity).getCameraProvider() 600 .getFirstBackCameraId(); 601 if (backCameraId != -1) { 602 cameraId = backCameraId; 603 } 604 } 605 return cameraId; 606 } 607 608 private static boolean isFrontCameraIntent(int intentCameraId) { 609 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT); 610 } 611 612 private static boolean isBackCameraIntent(int intentCameraId) { 613 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK); 614 } 615 616 private static int sLocation[] = new int[2]; 617 618 // This method is not thread-safe. 619 public static boolean pointInView(float x, float y, View v) { 620 v.getLocationInWindow(sLocation); 621 return x >= sLocation[0] && x < (sLocation[0] + v.getWidth()) 622 && y >= sLocation[1] && y < (sLocation[1] + v.getHeight()); 623 } 624 625 public static int[] getRelativeLocation(View reference, View view) { 626 reference.getLocationInWindow(sLocation); 627 int referenceX = sLocation[0]; 628 int referenceY = sLocation[1]; 629 view.getLocationInWindow(sLocation); 630 sLocation[0] -= referenceX; 631 sLocation[1] -= referenceY; 632 return sLocation; 633 } 634 635 public static boolean isUriValid(Uri uri, ContentResolver resolver) { 636 if (uri == null) return false; 637 638 try { 639 ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r"); 640 if (pfd == null) { 641 Log.e(TAG, "Fail to open URI. URI=" + uri); 642 return false; 643 } 644 pfd.close(); 645 } catch (IOException ex) { 646 return false; 647 } 648 return true; 649 } 650 651 public static void dumpRect(RectF rect, String msg) { 652 Log.v(TAG, msg + "=(" + rect.left + "," + rect.top 653 + "," + rect.right + "," + rect.bottom + ")"); 654 } 655 656 public static void rectFToRect(RectF rectF, Rect rect) { 657 rect.left = Math.round(rectF.left); 658 rect.top = Math.round(rectF.top); 659 rect.right = Math.round(rectF.right); 660 rect.bottom = Math.round(rectF.bottom); 661 } 662 663 public static Rect rectFToRect(RectF rectF) { 664 Rect rect = new Rect(); 665 rectFToRect(rectF, rect); 666 return rect; 667 } 668 669 public static RectF rectToRectF(Rect r) { 670 return new RectF(r.left, r.top, r.right, r.bottom); 671 } 672 673 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation, 674 int viewWidth, int viewHeight) { 675 // Need mirror for front camera. 676 matrix.setScale(mirror ? -1 : 1, 1); 677 // This is the value for android.hardware.Camera.setDisplayOrientation. 678 matrix.postRotate(displayOrientation); 679 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000). 680 // UI coordinates range from (0, 0) to (width, height). 681 matrix.postScale(viewWidth / 2000f, viewHeight / 2000f); 682 matrix.postTranslate(viewWidth / 2f, viewHeight / 2f); 683 } 684 685 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation, 686 Rect previewRect) { 687 // Need mirror for front camera. 688 matrix.setScale(mirror ? -1 : 1, 1); 689 // This is the value for android.hardware.Camera.setDisplayOrientation. 690 matrix.postRotate(displayOrientation); 691 692 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000). 693 // We need to map camera driver coordinates to preview rect coordinates 694 Matrix mapping = new Matrix(); 695 mapping.setRectToRect(new RectF(-1000, -1000, 1000, 1000), rectToRectF(previewRect), 696 Matrix.ScaleToFit.FILL); 697 matrix.setConcat(mapping, matrix); 698 } 699 700 public static String createJpegName(long dateTaken) { 701 synchronized (sImageFileNamer) { 702 return sImageFileNamer.generateName(dateTaken); 703 } 704 } 705 706 public static void broadcastNewPicture(Context context, Uri uri) { 707 context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri)); 708 // Keep compatibility 709 context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri)); 710 } 711 712 public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) { 713 if (view.getVisibility() == View.VISIBLE) return; 714 715 view.setVisibility(View.VISIBLE); 716 Animation animation = new AlphaAnimation(startAlpha, endAlpha); 717 animation.setDuration(duration); 718 view.startAnimation(animation); 719 } 720 721 public static void fadeIn(View view) { 722 fadeIn(view, 0F, 1F, 400); 723 724 // We disabled the button in fadeOut(), so enable it here. 725 view.setEnabled(true); 726 } 727 728 public static void fadeOut(View view) { 729 if (view.getVisibility() != View.VISIBLE) return; 730 731 // Since the button is still clickable before fade-out animation 732 // ends, we disable the button first to block click. 733 view.setEnabled(false); 734 Animation animation = new AlphaAnimation(1F, 0F); 735 animation.setDuration(400); 736 view.startAnimation(animation); 737 view.setVisibility(View.GONE); 738 } 739 740 public static int getJpegRotation(CameraActivity activity, int cameraId, int orientation) { 741 // See android.hardware.Camera.Parameters.setRotation for 742 // documentation. 743 int rotation = 0; 744 if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) { 745 CameraInfo info = activity.getCameraProvider().getCameraInfo()[cameraId]; 746 if (info.facing == CameraInfo.CAMERA_FACING_FRONT) { 747 rotation = (info.orientation - orientation + 360) % 360; 748 } else { // back-facing camera 749 rotation = (info.orientation + orientation) % 360; 750 } 751 } 752 return rotation; 753 } 754 755 /** 756 * Down-samples a jpeg byte array. 757 * @param data a byte array of jpeg data 758 * @param downSampleFactor down-sample factor 759 * @return decoded and down-sampled bitmap 760 */ 761 public static Bitmap downSample(final byte[] data, int downSampleFactor) { 762 final BitmapFactory.Options opts = new BitmapFactory.Options(); 763 // Downsample the image 764 opts.inSampleSize = downSampleFactor; 765 return BitmapFactory.decodeByteArray(data, 0, data.length, opts); 766 } 767 768 public static void setGpsParameters(Parameters parameters, Location loc) { 769 // Clear previous GPS location from the parameters. 770 parameters.removeGpsData(); 771 772 // We always encode GpsTimeStamp 773 parameters.setGpsTimestamp(System.currentTimeMillis() / 1000); 774 775 // Set GPS location. 776 if (loc != null) { 777 double lat = loc.getLatitude(); 778 double lon = loc.getLongitude(); 779 boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d); 780 781 if (hasLatLon) { 782 Log.d(TAG, "Set gps location"); 783 parameters.setGpsLatitude(lat); 784 parameters.setGpsLongitude(lon); 785 parameters.setGpsProcessingMethod(loc.getProvider().toUpperCase()); 786 if (loc.hasAltitude()) { 787 parameters.setGpsAltitude(loc.getAltitude()); 788 } else { 789 // for NETWORK_PROVIDER location provider, we may have 790 // no altitude information, but the driver needs it, so 791 // we fake one. 792 parameters.setGpsAltitude(0); 793 } 794 if (loc.getTime() != 0) { 795 // Location.getTime() is UTC in milliseconds. 796 // gps-timestamp is UTC in seconds. 797 long utcTimeSeconds = loc.getTime() / 1000; 798 parameters.setGpsTimestamp(utcTimeSeconds); 799 } 800 } else { 801 loc = null; 802 } 803 } 804 } 805 806 /** 807 * For still image capture, we need to get the right fps range such that the 808 * camera can slow down the framerate to allow for less-noisy/dark 809 * viewfinder output in dark conditions. 810 * 811 * @param params Camera's parameters. 812 * @return null if no appropiate fps range can't be found. Otherwise, return 813 * the right range. 814 */ 815 public static int[] getPhotoPreviewFpsRange(Parameters params) { 816 return getPhotoPreviewFpsRange(params.getSupportedPreviewFpsRange()); 817 } 818 819 public static int[] getPhotoPreviewFpsRange(List<int[]> frameRates) { 820 if (frameRates.size() == 0) { 821 Log.e(TAG, "No suppoted frame rates returned!"); 822 return null; 823 } 824 825 // Find the lowest min rate in supported ranges who can cover 30fps. 826 int lowestMinRate = MAX_PREVIEW_FPS_TIMES_1000; 827 for (int[] rate : frameRates) { 828 int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX]; 829 int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX]; 830 if (maxFps >= PREFERRED_PREVIEW_FPS_TIMES_1000 && 831 minFps <= PREFERRED_PREVIEW_FPS_TIMES_1000 && 832 minFps < lowestMinRate) { 833 lowestMinRate = minFps; 834 } 835 } 836 837 // Find all the modes with the lowest min rate found above, the pick the 838 // one with highest max rate. 839 int resultIndex = -1; 840 int highestMaxRate = 0; 841 for (int i = 0; i < frameRates.size(); i++) { 842 int[] rate = frameRates.get(i); 843 int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX]; 844 int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX]; 845 if (minFps == lowestMinRate && highestMaxRate < maxFps) { 846 highestMaxRate = maxFps; 847 resultIndex = i; 848 } 849 } 850 851 if (resultIndex >= 0) { 852 return frameRates.get(resultIndex); 853 } 854 Log.e(TAG, "Can't find an appropiate frame rate range!"); 855 return null; 856 } 857 858 public static int[] getMaxPreviewFpsRange(Parameters params) { 859 List<int[]> frameRates = params.getSupportedPreviewFpsRange(); 860 if (frameRates != null && frameRates.size() > 0) { 861 // The list is sorted. Return the last element. 862 return frameRates.get(frameRates.size() - 1); 863 } 864 return new int[0]; 865 } 866 867 public static void throwIfCameraDisabled(Context context) throws CameraDisabledException { 868 // Check if device policy has disabled the camera. 869 DevicePolicyManager dpm = 870 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 871 if (dpm.getCameraDisabled(null)) { 872 throw new CameraDisabledException(); 873 } 874 } 875 876 private static class ImageFileNamer { 877 private final SimpleDateFormat mFormat; 878 879 // The date (in milliseconds) used to generate the last name. 880 private long mLastDate; 881 882 // Number of names generated for the same second. 883 private int mSameSecondCount; 884 885 public ImageFileNamer(String format) { 886 mFormat = new SimpleDateFormat(format); 887 } 888 889 public String generateName(long dateTaken) { 890 Date date = new Date(dateTaken); 891 String result = mFormat.format(date); 892 893 // If the last name was generated for the same second, 894 // we append _1, _2, etc to the name. 895 if (dateTaken / 1000 == mLastDate / 1000) { 896 mSameSecondCount++; 897 result += "_" + mSameSecondCount; 898 } else { 899 mLastDate = dateTaken; 900 mSameSecondCount = 0; 901 } 902 903 return result; 904 } 905 } 906 907 public static void playVideo(Activity activity, Uri uri, String title) { 908 try { 909 boolean isSecureCamera = ((CameraActivity)activity).isSecureCamera(); 910 if (!isSecureCamera) { 911 Intent intent = IntentHelper.getVideoPlayerIntent(activity, uri) 912 .putExtra(Intent.EXTRA_TITLE, title) 913 .putExtra(KEY_TREAT_UP_AS_BACK, true); 914 activity.startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW); 915 } else { 916 // In order not to send out any intent to be intercepted and 917 // show the lock screen immediately, we just let the secure 918 // camera activity finish. 919 activity.finish(); 920 } 921 } catch (ActivityNotFoundException e) { 922 Toast.makeText(activity, activity.getString(R.string.video_err), 923 Toast.LENGTH_SHORT).show(); 924 } 925 } 926 927 /** 928 * Starts GMM with the given location shown. If this fails, and GMM could 929 * not be found, we use a geo intent as a fallback. 930 * 931 * @param activity the activity to use for launching the Maps intent. 932 * @param latLong a 2-element array containing {latitude/longitude}. 933 */ 934 public static void showOnMap(Activity activity, double[] latLong) { 935 try { 936 // We don't use "geo:latitude,longitude" because it only centers 937 // the MapView to the specified location, but we need a marker 938 // for further operations (routing to/from). 939 // The q=(lat, lng) syntax is suggested by geo-team. 940 String uri = String.format(Locale.ENGLISH, "http://maps.google.com/maps?f=q&q=(%f,%f)", 941 latLong[0], latLong[1]); 942 ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME, 943 MAPS_CLASS_NAME); 944 Intent mapsIntent = new Intent(Intent.ACTION_VIEW, 945 Uri.parse(uri)).setComponent(compName); 946 activity.startActivityForResult(mapsIntent, 947 CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW); 948 } catch (ActivityNotFoundException e) { 949 // Use the "geo intent" if no GMM is installed 950 Log.e(TAG, "GMM activity not found!", e); 951 String url = String.format(Locale.ENGLISH, "geo:%f,%f", latLong[0], latLong[1]); 952 Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); 953 activity.startActivity(mapsIntent); 954 } 955 } 956 957 /** 958 * Dumps the stack trace. 959 * 960 * @param level How many levels of the stack are dumped. 0 means all. 961 * @return A {@link java.lang.String} of all the output with newline 962 * between each. 963 */ 964 public static String dumpStackTrace(int level) { 965 StackTraceElement[] elems = Thread.currentThread().getStackTrace(); 966 // Ignore the first 3 elements. 967 level = (level == 0 ? elems.length : Math.min(level + 3, elems.length)); 968 String ret = new String(); 969 for (int i = 3; i < level; i++) { 970 ret = ret + "\t" + elems[i].toString() + '\n'; 971 } 972 return ret; 973 } 974 975 976 /** 977 * Gets the theme color of a specific mode. 978 * 979 * @param modeIndex index of the mode 980 * @param context current context 981 * @return theme color of the mode if input index is valid, otherwise 0 982 */ 983 public static int getCameraThemeColorId(int modeIndex, Context context) { 984 985 // Find the theme color using id from the color array 986 TypedArray colorRes = context.getResources() 987 .obtainTypedArray(R.array.camera_mode_theme_color); 988 if (modeIndex >= colorRes.length() || modeIndex < 0) { 989 // Mode index not found 990 Log.e(TAG, "Invalid mode index: " + modeIndex); 991 return 0; 992 } 993 return colorRes.getResourceId(modeIndex, 0); 994 } 995 996 /** 997 * Gets the theme pressed color of a specific mode. 998 * 999 * @param modeIndex index of the mode 1000 * @param context current context 1001 * @return theme pressed color of the mode if input index is valid, otherwise 0 1002 */ 1003 public static int getCameraThemePressedColorId(int modeIndex, Context context) { 1004 1005 // Find the theme color using id from the color array 1006 TypedArray colorRes = context.getResources() 1007 .obtainTypedArray(R.array.camera_mode_theme_pressed_color); 1008 if (modeIndex >= colorRes.length() || modeIndex < 0) { 1009 // Mode index not found 1010 Log.e(TAG, "Invalid mode index: " + modeIndex); 1011 return 0; 1012 } 1013 return colorRes.getResourceId(modeIndex, 0); 1014 } 1015 1016 /** 1017 * Gets the mode icon resource id of a specific mode. 1018 * 1019 * @param modeIndex index of the mode 1020 * @param context current context 1021 * @return icon resource id if the index is valid, otherwise 0 1022 */ 1023 public static int getCameraModeIconResId(int modeIndex, Context context) { 1024 // Find the camera mode icon using id 1025 TypedArray cameraModesIcons = context.getResources() 1026 .obtainTypedArray(R.array.camera_mode_icon); 1027 if (modeIndex >= cameraModesIcons.length() || modeIndex < 0) { 1028 // Mode index not found 1029 Log.e(TAG, "Invalid mode index: " + modeIndex); 1030 return 0; 1031 } 1032 return cameraModesIcons.getResourceId(modeIndex, 0); 1033 } 1034 1035 /** 1036 * Gets the mode text of a specific mode. 1037 * 1038 * @param modeIndex index of the mode 1039 * @param context current context 1040 * @return mode text if the index is valid, otherwise a new empty string 1041 */ 1042 public static String getCameraModeText(int modeIndex, Context context) { 1043 // Find the camera mode icon using id 1044 String[] cameraModesText = context.getResources() 1045 .getStringArray(R.array.camera_mode_text); 1046 if (modeIndex < 0 || modeIndex >= cameraModesText.length) { 1047 Log.e(TAG, "Invalid mode index: " + modeIndex); 1048 return new String(); 1049 } 1050 return cameraModesText[modeIndex]; 1051 } 1052 1053 /** 1054 * Gets the shutter icon res id for a specific mode. 1055 * 1056 * @param modeIndex index of the mode 1057 * @param context current context 1058 * @return mode shutter icon id if the index is valid, otherwise 0. 1059 */ 1060 public static int getCameraShutterIconId(int modeIndex, Context context) { 1061 // Find the camera mode icon using id 1062 TypedArray shutterIcons = context.getResources() 1063 .obtainTypedArray(R.array.camera_mode_shutter_icon); 1064 if (modeIndex < 0 || modeIndex >= shutterIcons.length()) { 1065 Log.e(TAG, "Invalid mode index: " + modeIndex); 1066 return 0; 1067 } 1068 return shutterIcons.getResourceId(modeIndex, 0); 1069 } 1070} 1071