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 17 package com.android.camera.util; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.app.admin.DevicePolicyManager; 22 import android.content.ActivityNotFoundException; 23 import android.content.ComponentName; 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.content.DialogInterface; 27 import android.content.Intent; 28 import android.content.res.TypedArray; 29 import android.graphics.Bitmap; 30 import android.graphics.BitmapFactory; 31 import android.graphics.Matrix; 32 import android.graphics.Point; 33 import android.graphics.PointF; 34 import android.graphics.Rect; 35 import android.graphics.RectF; 36 import android.hardware.camera2.CameraCharacteristics; 37 import android.hardware.camera2.CameraMetadata; 38 import android.location.Location; 39 import android.net.Uri; 40 import android.os.ParcelFileDescriptor; 41 import android.telephony.TelephonyManager; 42 import android.util.DisplayMetrics; 43 import android.util.FloatMath; 44 import android.util.TypedValue; 45 import android.view.Display; 46 import android.view.OrientationEventListener; 47 import android.view.Surface; 48 import android.view.View; 49 import android.view.WindowManager; 50 import android.view.animation.AlphaAnimation; 51 import android.view.animation.Animation; 52 import android.widget.Toast; 53 54 import com.android.camera.CameraActivity; 55 import com.android.camera.CameraDisabledException; 56 import com.android.camera.debug.Log; 57 import com.android.camera.filmstrip.ImageData; 58 import com.android.camera2.R; 59 import com.android.ex.camera2.portability.CameraCapabilities; 60 import com.android.ex.camera2.portability.CameraSettings; 61 62 import java.io.Closeable; 63 import java.io.IOException; 64 import java.lang.reflect.Method; 65 import java.text.SimpleDateFormat; 66 import java.util.Date; 67 import java.util.List; 68 import java.util.Locale; 69 70 /** 71 * Collection of utility functions used in this package. 72 */ 73 public class CameraUtil { 74 private static final Log.Tag TAG = new Log.Tag("Util"); 75 76 // For calculate the best fps range for still image capture. 77 private final static int MAX_PREVIEW_FPS_TIMES_1000 = 400000; 78 private final static int PREFERRED_PREVIEW_FPS_TIMES_1000 = 30000; 79 80 // For creating crop intents. 81 public static final String KEY_RETURN_DATA = "return-data"; 82 public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked"; 83 84 /** Orientation hysteresis amount used in rounding, in degrees. */ 85 public static final int ORIENTATION_HYSTERESIS = 5; 86 87 public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW"; 88 /** See android.hardware.Camera.ACTION_NEW_PICTURE. */ 89 public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE"; 90 /** See android.hardware.Camera.ACTION_NEW_VIDEO. */ 91 public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO"; 92 93 /** 94 * Broadcast Action: The camera application has become active in 95 * picture-taking mode. 96 */ 97 public static final String ACTION_CAMERA_STARTED = "com.android.camera.action.CAMERA_STARTED"; 98 /** 99 * Broadcast Action: The camera application is no longer in active 100 * picture-taking mode. 101 */ 102 public static final String ACTION_CAMERA_STOPPED = "com.android.camera.action.CAMERA_STOPPED"; 103 /** 104 * When the camera application is active in picture-taking mode, it listens 105 * for this intent, which upon receipt will trigger the shutter to capture a 106 * new picture, as if the user had pressed the shutter button. 107 */ 108 public static final String ACTION_CAMERA_SHUTTER_CLICK = 109 "com.android.camera.action.SHUTTER_CLICK"; 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 /** Private intent extras. Test only. */ 119 private static final String EXTRAS_CAMERA_FACING = 120 "android.intent.extras.CAMERA_FACING"; 121 122 private static float sPixelDensity = 1; 123 private static ImageFileNamer sImageFileNamer; 124 125 private CameraUtil() { 126 } 127 128 public static void initialize(Context context) { 129 DisplayMetrics metrics = new DisplayMetrics(); 130 WindowManager wm = (WindowManager) 131 context.getSystemService(Context.WINDOW_SERVICE); 132 wm.getDefaultDisplay().getMetrics(metrics); 133 sPixelDensity = metrics.density; 134 sImageFileNamer = new ImageFileNamer( 135 context.getString(R.string.image_file_name_format)); 136 } 137 138 public static int dpToPixel(int dp) { 139 return Math.round(sPixelDensity * dp); 140 } 141 142 /** 143 * Rotates the bitmap by the specified degree. If a new bitmap is created, 144 * the original bitmap is recycled. 145 */ 146 public static Bitmap rotate(Bitmap b, int degrees) { 147 return rotateAndMirror(b, degrees, false); 148 } 149 150 /** 151 * Rotates and/or mirrors the bitmap. If a new bitmap is created, the 152 * original bitmap is recycled. 153 */ 154 public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) { 155 if ((degrees != 0 || mirror) && b != null) { 156 Matrix m = new Matrix(); 157 // Mirror first. 158 // horizontal flip + rotation = -rotation + horizontal flip 159 if (mirror) { 160 m.postScale(-1, 1); 161 degrees = (degrees + 360) % 360; 162 if (degrees == 0 || degrees == 180) { 163 m.postTranslate(b.getWidth(), 0); 164 } else if (degrees == 90 || degrees == 270) { 165 m.postTranslate(b.getHeight(), 0); 166 } else { 167 throw new IllegalArgumentException("Invalid degrees=" + degrees); 168 } 169 } 170 if (degrees != 0) { 171 // clockwise 172 m.postRotate(degrees, 173 (float) b.getWidth() / 2, (float) b.getHeight() / 2); 174 } 175 176 try { 177 Bitmap b2 = Bitmap.createBitmap( 178 b, 0, 0, b.getWidth(), b.getHeight(), m, true); 179 if (b != b2) { 180 b.recycle(); 181 b = b2; 182 } 183 } catch (OutOfMemoryError ex) { 184 // We have no memory to rotate. Return the original bitmap. 185 } 186 } 187 return b; 188 } 189 190 /** 191 * Compute the sample size as a function of minSideLength and 192 * maxNumOfPixels. minSideLength is used to specify that minimal width or 193 * height of a bitmap. maxNumOfPixels is used to specify the maximal size in 194 * pixels that is tolerable in terms of memory usage. The function returns a 195 * sample size based on the constraints. 196 * <p> 197 * Both size and minSideLength can be passed in as -1 which indicates no 198 * care of the corresponding constraint. The functions prefers returning a 199 * sample size that generates a smaller bitmap, unless minSideLength = -1. 200 * <p> 201 * Also, the function rounds up the sample size to a power of 2 or multiple 202 * of 8 because BitmapFactory only honors sample size this way. For example, 203 * BitmapFactory downsamples an image by 2 even though the request is 3. So 204 * we round up the sample size to avoid OOM. 205 */ 206 public static int computeSampleSize(BitmapFactory.Options options, 207 int minSideLength, int maxNumOfPixels) { 208 int initialSize = computeInitialSampleSize(options, minSideLength, 209 maxNumOfPixels); 210 211 int roundedSize; 212 if (initialSize <= 8) { 213 roundedSize = 1; 214 while (roundedSize < initialSize) { 215 roundedSize <<= 1; 216 } 217 } else { 218 roundedSize = (initialSize + 7) / 8 * 8; 219 } 220 221 return roundedSize; 222 } 223 224 private static int computeInitialSampleSize(BitmapFactory.Options options, 225 int minSideLength, int maxNumOfPixels) { 226 double w = options.outWidth; 227 double h = options.outHeight; 228 229 int lowerBound = (maxNumOfPixels < 0) ? 1 : 230 (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels)); 231 int upperBound = (minSideLength < 0) ? 128 : 232 (int) Math.min(Math.floor(w / minSideLength), 233 Math.floor(h / minSideLength)); 234 235 if (upperBound < lowerBound) { 236 // return the larger one when there is no overlapping zone. 237 return lowerBound; 238 } 239 240 if (maxNumOfPixels < 0 && minSideLength < 0) { 241 return 1; 242 } else if (minSideLength < 0) { 243 return lowerBound; 244 } else { 245 return upperBound; 246 } 247 } 248 249 public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) { 250 try { 251 BitmapFactory.Options options = new BitmapFactory.Options(); 252 options.inJustDecodeBounds = true; 253 BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, 254 options); 255 if (options.mCancel || options.outWidth == -1 256 || options.outHeight == -1) { 257 return null; 258 } 259 options.inSampleSize = computeSampleSize( 260 options, -1, maxNumOfPixels); 261 options.inJustDecodeBounds = false; 262 263 options.inDither = false; 264 options.inPreferredConfig = Bitmap.Config.ARGB_8888; 265 return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, 266 options); 267 } catch (OutOfMemoryError ex) { 268 Log.e(TAG, "Got oom exception ", ex); 269 return null; 270 } 271 } 272 273 public static void closeSilently(Closeable c) { 274 if (c == null) { 275 return; 276 } 277 try { 278 c.close(); 279 } catch (Throwable t) { 280 // do nothing 281 } 282 } 283 284 public static void Assert(boolean cond) { 285 if (!cond) { 286 throw new AssertionError(); 287 } 288 } 289 290 public static void showErrorAndFinish(final Activity activity, int msgId) { 291 DialogInterface.OnClickListener buttonListener = 292 new DialogInterface.OnClickListener() { 293 @Override 294 public void onClick(DialogInterface dialog, int which) { 295 activity.finish(); 296 } 297 }; 298 TypedValue out = new TypedValue(); 299 activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true); 300 // Some crash reports indicate users leave app prior to this dialog 301 // appearing, so check to ensure that the activity is not shutting down 302 // before attempting to attach a dialog to the window manager. 303 if (!activity.isFinishing()) { 304 new AlertDialog.Builder(activity) 305 .setCancelable(false) 306 .setTitle(R.string.camera_error_title) 307 .setMessage(msgId) 308 .setNeutralButton(R.string.dialog_ok, buttonListener) 309 .setIcon(out.resourceId) 310 .show(); 311 } 312 } 313 314 public static <T> T checkNotNull(T object) { 315 if (object == null) { 316 throw new NullPointerException(); 317 } 318 return object; 319 } 320 321 public static boolean equals(Object a, Object b) { 322 return (a == b) || (a == null ? false : a.equals(b)); 323 } 324 325 public static int nextPowerOf2(int n) { 326 // TODO: what happens if n is negative or already a power of 2? 327 n -= 1; 328 n |= n >>> 16; 329 n |= n >>> 8; 330 n |= n >>> 4; 331 n |= n >>> 2; 332 n |= n >>> 1; 333 return n + 1; 334 } 335 336 public static float distance(float x, float y, float sx, float sy) { 337 float dx = x - sx; 338 float dy = y - sy; 339 return (float) Math.sqrt(dx * dx + dy * dy); 340 } 341 342 /** 343 * Clamps x to between min and max (inclusive on both ends, x = min --> min, 344 * x = max --> max). 345 */ 346 public static int clamp(int x, int min, int max) { 347 if (x > max) { 348 return max; 349 } 350 if (x < min) { 351 return min; 352 } 353 return x; 354 } 355 356 /** 357 * Clamps x to between min and max (inclusive on both ends, x = min --> min, 358 * x = max --> max). 359 */ 360 public static float clamp(float x, float min, float max) { 361 if (x > max) { 362 return max; 363 } 364 if (x < min) { 365 return min; 366 } 367 return x; 368 } 369 370 /** 371 * Linear interpolation between a and b by the fraction t. t = 0 --> a, t = 372 * 1 --> b. 373 */ 374 public static float lerp(float a, float b, float t) { 375 return a + t * (b - a); 376 } 377 378 /** 379 * Given (nx, ny) \in [0, 1]^2, in the display's portrait coordinate system, 380 * returns normalized sensor coordinates \in [0, 1]^2 depending on how 381 * the sensor's orientation \in {0, 90, 180, 270}. 382 * 383 * <p> 384 * Returns null if sensorOrientation is not one of the above. 385 * </p> 386 */ 387 public static PointF normalizedSensorCoordsForNormalizedDisplayCoords( 388 float nx, float ny, int sensorOrientation) { 389 switch (sensorOrientation) { 390 case 0: 391 return new PointF(nx, ny); 392 case 90: 393 return new PointF(ny, 1.0f - nx); 394 case 180: 395 return new PointF(1.0f - nx, 1.0f - ny); 396 case 270: 397 return new PointF(1.0f - ny, nx); 398 default: 399 return null; 400 } 401 } 402 403 /** 404 * Given a size, return the largest size with the given aspectRatio that 405 * maximally fits into the bounding rectangle of the original Size. 406 * 407 * @param size the original Size to crop 408 * @param aspectRatio the target aspect ratio 409 * @return the largest Size with the given aspect ratio that is smaller than 410 * or equal to the original Size. 411 */ 412 public static Size constrainToAspectRatio(Size size, float aspectRatio) { 413 float width = size.getWidth(); 414 float height = size.getHeight(); 415 416 float currentAspectRatio = width * 1.0f / height; 417 418 if (currentAspectRatio > aspectRatio) { 419 // chop longer side 420 if (width > height) { 421 width = height * aspectRatio; 422 } else { 423 height = width / aspectRatio; 424 } 425 } else if (currentAspectRatio < aspectRatio) { 426 // chop shorter side 427 if (width < height) { 428 width = height * aspectRatio; 429 } else { 430 height = width / aspectRatio; 431 } 432 } 433 434 return new Size((int) width, (int) height); 435 } 436 437 public static int getDisplayRotation(Context context) { 438 WindowManager windowManager = (WindowManager) context 439 .getSystemService(Context.WINDOW_SERVICE); 440 int rotation = windowManager.getDefaultDisplay() 441 .getRotation(); 442 switch (rotation) { 443 case Surface.ROTATION_0: 444 return 0; 445 case Surface.ROTATION_90: 446 return 90; 447 case Surface.ROTATION_180: 448 return 180; 449 case Surface.ROTATION_270: 450 return 270; 451 } 452 return 0; 453 } 454 455 /** 456 * Calculate the default orientation of the device based on the width and 457 * height of the display when rotation = 0 (i.e. natural width and height) 458 * 459 * @param context current context 460 * @return whether the default orientation of the device is portrait 461 */ 462 public static boolean isDefaultToPortrait(Context context) { 463 Display currentDisplay = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)) 464 .getDefaultDisplay(); 465 Point displaySize = new Point(); 466 currentDisplay.getSize(displaySize); 467 int orientation = currentDisplay.getRotation(); 468 int naturalWidth, naturalHeight; 469 if (orientation == Surface.ROTATION_0 || orientation == Surface.ROTATION_180) { 470 naturalWidth = displaySize.x; 471 naturalHeight = displaySize.y; 472 } else { 473 naturalWidth = displaySize.y; 474 naturalHeight = displaySize.x; 475 } 476 return naturalWidth < naturalHeight; 477 } 478 479 public static int roundOrientation(int orientation, int orientationHistory) { 480 boolean changeOrientation = false; 481 if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) { 482 changeOrientation = true; 483 } else { 484 int dist = Math.abs(orientation - orientationHistory); 485 dist = Math.min(dist, 360 - dist); 486 changeOrientation = (dist >= 45 + ORIENTATION_HYSTERESIS); 487 } 488 if (changeOrientation) { 489 return ((orientation + 45) / 90 * 90) % 360; 490 } 491 return orientationHistory; 492 } 493 494 private static Size getDefaultDisplaySize(Context context) { 495 WindowManager windowManager = (WindowManager) context 496 .getSystemService(Context.WINDOW_SERVICE); 497 Point res = new Point(); 498 windowManager.getDefaultDisplay().getSize(res); 499 return new Size(res); 500 } 501 502 public static com.android.ex.camera2.portability.Size getOptimalPreviewSize(Context context, 503 List<com.android.ex.camera2.portability.Size> sizes, double targetRatio) { 504 int optimalPickIndex = getOptimalPreviewSizeIndex(context, Size.convert(sizes), 505 targetRatio); 506 if (optimalPickIndex == -1) { 507 return null; 508 } else { 509 return sizes.get(optimalPickIndex); 510 } 511 } 512 513 public static int getOptimalPreviewSizeIndex(Context context, 514 List<Size> sizes, double targetRatio) { 515 // Use a very small tolerance because we want an exact match. 516 final double ASPECT_TOLERANCE = 0.01; 517 if (sizes == null) { 518 return -1; 519 } 520 521 int optimalSizeIndex = -1; 522 double minDiff = Double.MAX_VALUE; 523 524 // Because of bugs of overlay and layout, we sometimes will try to 525 // layout the viewfinder in the portrait orientation and thus get the 526 // wrong size of preview surface. When we change the preview size, the 527 // new overlay will be created before the old one closed, which causes 528 // an exception. For now, just get the screen size. 529 Size defaultDisplaySize = getDefaultDisplaySize(context); 530 int targetHeight = Math.min(defaultDisplaySize.getWidth(), defaultDisplaySize.getHeight()); 531 // Try to find an size match aspect ratio and size 532 for (int i = 0; i < sizes.size(); i++) { 533 Size size = sizes.get(i); 534 double ratio = (double) size.getWidth() / size.getHeight(); 535 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) { 536 continue; 537 } 538 539 double heightDiff = Math.abs(size.getHeight() - targetHeight); 540 if (heightDiff < minDiff) { 541 optimalSizeIndex = i; 542 minDiff = heightDiff; 543 } else if (heightDiff == minDiff) { 544 // Prefer resolutions smaller-than-display when an equally close 545 // larger-than-display resolution is available 546 if (size.getHeight() < targetHeight) { 547 optimalSizeIndex = i; 548 minDiff = heightDiff; 549 } 550 } 551 } 552 // Cannot find the one match the aspect ratio. This should not happen. 553 // Ignore the requirement. 554 if (optimalSizeIndex == -1) { 555 Log.w(TAG, "No preview size match the aspect ratio"); 556 minDiff = Double.MAX_VALUE; 557 for (int i = 0; i < sizes.size(); i++) { 558 Size size = sizes.get(i); 559 if (Math.abs(size.getHeight() - targetHeight) < minDiff) { 560 optimalSizeIndex = i; 561 minDiff = Math.abs(size.getHeight() - targetHeight); 562 } 563 } 564 } 565 return optimalSizeIndex; 566 } 567 568 /** Returns the largest picture size which matches the given aspect ratio. */ 569 public static com.android.ex.camera2.portability.Size getOptimalVideoSnapshotPictureSize( 570 List<com.android.ex.camera2.portability.Size> sizes, double targetRatio) { 571 // Use a very small tolerance because we want an exact match. 572 final double ASPECT_TOLERANCE = 0.001; 573 if (sizes == null) { 574 return null; 575 } 576 577 com.android.ex.camera2.portability.Size optimalSize = null; 578 579 // Try to find a size matches aspect ratio and has the largest width 580 for (com.android.ex.camera2.portability.Size size : sizes) { 581 double ratio = (double) size.width() / size.height(); 582 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) { 583 continue; 584 } 585 if (optimalSize == null || size.width() > optimalSize.width()) { 586 optimalSize = size; 587 } 588 } 589 590 // Cannot find one that matches the aspect ratio. This should not 591 // happen. Ignore the requirement. 592 if (optimalSize == null) { 593 Log.w(TAG, "No picture size match the aspect ratio"); 594 for (com.android.ex.camera2.portability.Size size : sizes) { 595 if (optimalSize == null || size.width() > optimalSize.width()) { 596 optimalSize = size; 597 } 598 } 599 } 600 return optimalSize; 601 } 602 603 /** 604 * Returns whether the device is voice-capable (meaning, it can do MMS). 605 */ 606 public static boolean isMmsCapable(Context context) { 607 TelephonyManager telephonyManager = (TelephonyManager) 608 context.getSystemService(Context.TELEPHONY_SERVICE); 609 if (telephonyManager == null) { 610 return false; 611 } 612 613 try { 614 Class<?> partypes[] = new Class[0]; 615 Method sIsVoiceCapable = TelephonyManager.class.getMethod( 616 "isVoiceCapable", partypes); 617 618 Object arglist[] = new Object[0]; 619 Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist); 620 return (Boolean) retobj; 621 } catch (java.lang.reflect.InvocationTargetException ite) { 622 // Failure, must be another device. 623 // Assume that it is voice capable. 624 } catch (IllegalAccessException iae) { 625 // Failure, must be an other device. 626 // Assume that it is voice capable. 627 } catch (NoSuchMethodException nsme) { 628 } 629 return true; 630 } 631 632 // This is for test only. Allow the camera to launch the specific camera. 633 public static int getCameraFacingIntentExtras(Activity currentActivity) { 634 int cameraId = -1; 635 636 int intentCameraId = 637 currentActivity.getIntent().getIntExtra(CameraUtil.EXTRAS_CAMERA_FACING, -1); 638 639 if (isFrontCameraIntent(intentCameraId)) { 640 // Check if the front camera exist 641 int frontCameraId = ((CameraActivity) currentActivity).getCameraProvider() 642 .getFirstFrontCameraId(); 643 if (frontCameraId != -1) { 644 cameraId = frontCameraId; 645 } 646 } else if (isBackCameraIntent(intentCameraId)) { 647 // Check if the back camera exist 648 int backCameraId = ((CameraActivity) currentActivity).getCameraProvider() 649 .getFirstBackCameraId(); 650 if (backCameraId != -1) { 651 cameraId = backCameraId; 652 } 653 } 654 return cameraId; 655 } 656 657 private static boolean isFrontCameraIntent(int intentCameraId) { 658 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT); 659 } 660 661 private static boolean isBackCameraIntent(int intentCameraId) { 662 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK); 663 } 664 665 private static int sLocation[] = new int[2]; 666 667 // This method is not thread-safe. 668 public static boolean pointInView(float x, float y, View v) { 669 v.getLocationInWindow(sLocation); 670 return x >= sLocation[0] && x < (sLocation[0] + v.getWidth()) 671 && y >= sLocation[1] && y < (sLocation[1] + v.getHeight()); 672 } 673 674 public static int[] getRelativeLocation(View reference, View view) { 675 reference.getLocationInWindow(sLocation); 676 int referenceX = sLocation[0]; 677 int referenceY = sLocation[1]; 678 view.getLocationInWindow(sLocation); 679 sLocation[0] -= referenceX; 680 sLocation[1] -= referenceY; 681 return sLocation; 682 } 683 684 public static boolean isUriValid(Uri uri, ContentResolver resolver) { 685 if (uri == null) { 686 return false; 687 } 688 689 try { 690 ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r"); 691 if (pfd == null) { 692 Log.e(TAG, "Fail to open URI. URI=" + uri); 693 return false; 694 } 695 pfd.close(); 696 } catch (IOException ex) { 697 return false; 698 } 699 return true; 700 } 701 702 public static void dumpRect(RectF rect, String msg) { 703 Log.v(TAG, msg + "=(" + rect.left + "," + rect.top 704 + "," + rect.right + "," + rect.bottom + ")"); 705 } 706 707 public static void rectFToRect(RectF rectF, Rect rect) { 708 rect.left = Math.round(rectF.left); 709 rect.top = Math.round(rectF.top); 710 rect.right = Math.round(rectF.right); 711 rect.bottom = Math.round(rectF.bottom); 712 } 713 714 public static Rect rectFToRect(RectF rectF) { 715 Rect rect = new Rect(); 716 rectFToRect(rectF, rect); 717 return rect; 718 } 719 720 public static RectF rectToRectF(Rect r) { 721 return new RectF(r.left, r.top, r.right, r.bottom); 722 } 723 724 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation, 725 int viewWidth, int viewHeight) { 726 // Need mirror for front camera. 727 matrix.setScale(mirror ? -1 : 1, 1); 728 // This is the value for android.hardware.Camera.setDisplayOrientation. 729 matrix.postRotate(displayOrientation); 730 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000). 731 // UI coordinates range from (0, 0) to (width, height). 732 matrix.postScale(viewWidth / 2000f, viewHeight / 2000f); 733 matrix.postTranslate(viewWidth / 2f, viewHeight / 2f); 734 } 735 736 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation, 737 Rect previewRect) { 738 // Need mirror for front camera. 739 matrix.setScale(mirror ? -1 : 1, 1); 740 // This is the value for android.hardware.Camera.setDisplayOrientation. 741 matrix.postRotate(displayOrientation); 742 743 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000). 744 // We need to map camera driver coordinates to preview rect coordinates 745 Matrix mapping = new Matrix(); 746 mapping.setRectToRect(new RectF(-1000, -1000, 1000, 1000), rectToRectF(previewRect), 747 Matrix.ScaleToFit.FILL); 748 matrix.setConcat(mapping, matrix); 749 } 750 751 public static String createJpegName(long dateTaken) { 752 synchronized (sImageFileNamer) { 753 return sImageFileNamer.generateName(dateTaken); 754 } 755 } 756 757 public static void broadcastNewPicture(Context context, Uri uri) { 758 context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri)); 759 // Keep compatibility 760 context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri)); 761 } 762 763 public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) { 764 if (view.getVisibility() == View.VISIBLE) { 765 return; 766 } 767 768 view.setVisibility(View.VISIBLE); 769 Animation animation = new AlphaAnimation(startAlpha, endAlpha); 770 animation.setDuration(duration); 771 view.startAnimation(animation); 772 } 773 774 /** 775 * Down-samples a jpeg byte array. 776 * 777 * @param data a byte array of jpeg data 778 * @param downSampleFactor down-sample factor 779 * @return decoded and down-sampled bitmap 780 */ 781 public static Bitmap downSample(final byte[] data, int downSampleFactor) { 782 final BitmapFactory.Options opts = new BitmapFactory.Options(); 783 // Downsample the image 784 opts.inSampleSize = downSampleFactor; 785 return BitmapFactory.decodeByteArray(data, 0, data.length, opts); 786 } 787 788 public static void setGpsParameters(CameraSettings settings, Location loc) { 789 // Clear previous GPS location from the parameters. 790 settings.clearGpsData(); 791 792 boolean hasLatLon = false; 793 double lat; 794 double lon; 795 // Set GPS location. 796 if (loc != null) { 797 lat = loc.getLatitude(); 798 lon = loc.getLongitude(); 799 hasLatLon = (lat != 0.0d) || (lon != 0.0d); 800 } 801 802 if (!hasLatLon) { 803 // We always encode GpsTimeStamp even if the GPS location is not 804 // available. 805 settings.setGpsData( 806 new CameraSettings.GpsData(0f, 0f, 0f, System.currentTimeMillis() / 1000, null) 807 ); 808 } else { 809 Log.d(TAG, "Set gps location"); 810 // for NETWORK_PROVIDER location provider, we may have 811 // no altitude information, but the driver needs it, so 812 // we fake one. 813 // Location.getTime() is UTC in milliseconds. 814 // gps-timestamp is UTC in seconds. 815 long utcTimeSeconds = loc.getTime() / 1000; 816 settings.setGpsData(new CameraSettings.GpsData(loc.getLatitude(), loc.getLongitude(), 817 (loc.hasAltitude() ? loc.getAltitude() : 0), 818 (utcTimeSeconds != 0 ? utcTimeSeconds : System.currentTimeMillis()), 819 loc.getProvider().toUpperCase())); 820 } 821 } 822 823 /** 824 * For still image capture, we need to get the right fps range such that the 825 * camera can slow down the framerate to allow for less-noisy/dark 826 * viewfinder output in dark conditions. 827 * 828 * @param capabilities Camera's capabilities. 829 * @return null if no appropiate fps range can't be found. Otherwise, return 830 * the right range. 831 */ 832 public static int[] getPhotoPreviewFpsRange(CameraCapabilities capabilities) { 833 return getPhotoPreviewFpsRange(capabilities.getSupportedPreviewFpsRange()); 834 } 835 836 public static int[] getPhotoPreviewFpsRange(List<int[]> frameRates) { 837 if (frameRates.size() == 0) { 838 Log.e(TAG, "No suppoted frame rates returned!"); 839 return null; 840 } 841 842 // Find the lowest min rate in supported ranges who can cover 30fps. 843 int lowestMinRate = MAX_PREVIEW_FPS_TIMES_1000; 844 for (int[] rate : frameRates) { 845 int minFps = rate[0]; 846 int maxFps = rate[1]; 847 if (maxFps >= PREFERRED_PREVIEW_FPS_TIMES_1000 && 848 minFps <= PREFERRED_PREVIEW_FPS_TIMES_1000 && 849 minFps < lowestMinRate) { 850 lowestMinRate = minFps; 851 } 852 } 853 854 // Find all the modes with the lowest min rate found above, the pick the 855 // one with highest max rate. 856 int resultIndex = -1; 857 int highestMaxRate = 0; 858 for (int i = 0; i < frameRates.size(); i++) { 859 int[] rate = frameRates.get(i); 860 int minFps = rate[0]; 861 int maxFps = rate[1]; 862 if (minFps == lowestMinRate && highestMaxRate < maxFps) { 863 highestMaxRate = maxFps; 864 resultIndex = i; 865 } 866 } 867 868 if (resultIndex >= 0) { 869 return frameRates.get(resultIndex); 870 } 871 Log.e(TAG, "Can't find an appropiate frame rate range!"); 872 return null; 873 } 874 875 public static int[] getMaxPreviewFpsRange(List<int[]> frameRates) { 876 if (frameRates != null && frameRates.size() > 0) { 877 // The list is sorted. Return the last element. 878 return frameRates.get(frameRates.size() - 1); 879 } 880 return new int[0]; 881 } 882 883 public static void throwIfCameraDisabled(Context context) throws CameraDisabledException { 884 // Check if device policy has disabled the camera. 885 DevicePolicyManager dpm = 886 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 887 if (dpm.getCameraDisabled(null)) { 888 throw new CameraDisabledException(); 889 } 890 } 891 892 /** 893 * Generates a 1d Gaussian mask of the input array size, and store the mask 894 * in the input array. 895 * 896 * @param mask empty array of size n, where n will be used as the size of 897 * the Gaussian mask, and the array will be populated with the 898 * values of the mask. 899 */ 900 private static void getGaussianMask(float[] mask) { 901 int len = mask.length; 902 int mid = len / 2; 903 float sigma = len; 904 float sum = 0; 905 for (int i = 0; i <= mid; i++) { 906 float ex = FloatMath.exp(-(i - mid) * (i - mid) / (mid * mid)) 907 / (2 * sigma * sigma); 908 int symmetricIndex = len - 1 - i; 909 mask[i] = ex; 910 mask[symmetricIndex] = ex; 911 sum += mask[i]; 912 if (i != symmetricIndex) { 913 sum += mask[symmetricIndex]; 914 } 915 } 916 917 for (int i = 0; i < mask.length; i++) { 918 mask[i] /= sum; 919 } 920 921 } 922 923 /** 924 * Add two pixels together where the second pixel will be applied with a 925 * weight. 926 * 927 * @param pixel pixel color value of weight 1 928 * @param newPixel second pixel color value where the weight will be applied 929 * @param weight a float weight that will be applied to the second pixel 930 * color 931 * @return the weighted addition of the two pixels 932 */ 933 public static int addPixel(int pixel, int newPixel, float weight) { 934 // TODO: scale weight to [0, 1024] to avoid casting to float and back to 935 // int. 936 int r = ((pixel & 0x00ff0000) + (int) ((newPixel & 0x00ff0000) * weight)) & 0x00ff0000; 937 int g = ((pixel & 0x0000ff00) + (int) ((newPixel & 0x0000ff00) * weight)) & 0x0000ff00; 938 int b = ((pixel & 0x000000ff) + (int) ((newPixel & 0x000000ff) * weight)) & 0x000000ff; 939 return 0xff000000 | r | g | b; 940 } 941 942 /** 943 * Apply blur to the input image represented in an array of colors and put 944 * the output image, in the form of an array of colors, into the output 945 * array. 946 * 947 * @param src source array of colors 948 * @param out output array of colors after the blur 949 * @param w width of the image 950 * @param h height of the image 951 * @param size size of the Gaussian blur mask 952 */ 953 public static void blur(int[] src, int[] out, int w, int h, int size) { 954 float[] k = new float[size]; 955 int off = size / 2; 956 957 getGaussianMask(k); 958 959 int[] tmp = new int[src.length]; 960 961 // Apply the 1d Gaussian mask horizontally to the image and put the 962 // intermediat results in a temporary array. 963 int rowPointer = 0; 964 for (int y = 0; y < h; y++) { 965 for (int x = 0; x < w; x++) { 966 int sum = 0; 967 for (int i = 0; i < k.length; i++) { 968 int dx = x + i - off; 969 dx = clamp(dx, 0, w - 1); 970 sum = addPixel(sum, src[rowPointer + dx], k[i]); 971 } 972 tmp[x + rowPointer] = sum; 973 } 974 rowPointer += w; 975 } 976 977 // Apply the 1d Gaussian mask vertically to the intermediate array, and 978 // the final results will be stored in the output array. 979 for (int x = 0; x < w; x++) { 980 rowPointer = 0; 981 for (int y = 0; y < h; y++) { 982 int sum = 0; 983 for (int i = 0; i < k.length; i++) { 984 int dy = y + i - off; 985 dy = clamp(dy, 0, h - 1); 986 sum = addPixel(sum, tmp[dy * w + x], k[i]); 987 } 988 out[x + rowPointer] = sum; 989 rowPointer += w; 990 } 991 } 992 } 993 994 /** 995 * Calculates a new dimension to fill the bound with the original aspect 996 * ratio preserved. 997 * 998 * @param imageWidth The original width. 999 * @param imageHeight The original height. 1000 * @param imageRotation The clockwise rotation in degrees of the image which 1001 * the original dimension comes from. 1002 * @param boundWidth The width of the bound. 1003 * @param boundHeight The height of the bound. 1004 * @returns The final width/height stored in Point.x/Point.y to fill the 1005 * bounds and preserve image aspect ratio. 1006 */ 1007 public static Point resizeToFill(int imageWidth, int imageHeight, int imageRotation, 1008 int boundWidth, int boundHeight) { 1009 if (imageRotation % 180 != 0) { 1010 // Swap width and height. 1011 int savedWidth = imageWidth; 1012 imageWidth = imageHeight; 1013 imageHeight = savedWidth; 1014 } 1015 if (imageWidth == ImageData.SIZE_FULL 1016 || imageHeight == ImageData.SIZE_FULL) { 1017 imageWidth = boundWidth; 1018 imageHeight = boundHeight; 1019 } 1020 1021 Point p = new Point(); 1022 p.x = boundWidth; 1023 p.y = boundHeight; 1024 1025 if (imageWidth * boundHeight > boundWidth * imageHeight) { 1026 p.y = imageHeight * p.x / imageWidth; 1027 } else { 1028 p.x = imageWidth * p.y / imageHeight; 1029 } 1030 1031 return p; 1032 } 1033 1034 private static class ImageFileNamer { 1035 private final SimpleDateFormat mFormat; 1036 1037 // The date (in milliseconds) used to generate the last name. 1038 private long mLastDate; 1039 1040 // Number of names generated for the same second. 1041 private int mSameSecondCount; 1042 1043 public ImageFileNamer(String format) { 1044 mFormat = new SimpleDateFormat(format); 1045 } 1046 1047 public String generateName(long dateTaken) { 1048 Date date = new Date(dateTaken); 1049 String result = mFormat.format(date); 1050 1051 // If the last name was generated for the same second, 1052 // we append _1, _2, etc to the name. 1053 if (dateTaken / 1000 == mLastDate / 1000) { 1054 mSameSecondCount++; 1055 result += "_" + mSameSecondCount; 1056 } else { 1057 mLastDate = dateTaken; 1058 mSameSecondCount = 0; 1059 } 1060 1061 return result; 1062 } 1063 } 1064 1065 public static void playVideo(Activity activity, Uri uri, String title) { 1066 try { 1067 CameraActivity cameraActivity = (CameraActivity)activity; 1068 boolean isSecureCamera = cameraActivity.isSecureCamera(); 1069 if (!isSecureCamera) { 1070 Intent intent = IntentHelper.getVideoPlayerIntent(uri) 1071 .putExtra(Intent.EXTRA_TITLE, title) 1072 .putExtra(KEY_TREAT_UP_AS_BACK, true); 1073 cameraActivity.launchActivityByIntent(intent); 1074 } else { 1075 // In order not to send out any intent to be intercepted and 1076 // show the lock screen immediately, we just let the secure 1077 // camera activity finish. 1078 activity.finish(); 1079 } 1080 } catch (ActivityNotFoundException e) { 1081 Toast.makeText(activity, activity.getString(R.string.video_err), 1082 Toast.LENGTH_SHORT).show(); 1083 } 1084 } 1085 1086 /** 1087 * Starts GMM with the given location shown. If this fails, and GMM could 1088 * not be found, we use a geo intent as a fallback. 1089 * 1090 * @param activity the activity to use for launching the Maps intent. 1091 * @param latLong a 2-element array containing {latitude/longitude}. 1092 */ 1093 public static void showOnMap(Activity activity, double[] latLong) { 1094 try { 1095 // We don't use "geo:latitude,longitude" because it only centers 1096 // the MapView to the specified location, but we need a marker 1097 // for further operations (routing to/from). 1098 // The q=(lat, lng) syntax is suggested by geo-team. 1099 String uri = String.format(Locale.ENGLISH, "http://maps.google.com/maps?f=q&q=(%f,%f)", 1100 latLong[0], latLong[1]); 1101 ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME, 1102 MAPS_CLASS_NAME); 1103 Intent mapsIntent = new Intent(Intent.ACTION_VIEW, 1104 Uri.parse(uri)).setComponent(compName); 1105 mapsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); 1106 activity.startActivity(mapsIntent); 1107 } catch (ActivityNotFoundException e) { 1108 // Use the "geo intent" if no GMM is installed 1109 Log.e(TAG, "GMM activity not found!", e); 1110 String url = String.format(Locale.ENGLISH, "geo:%f,%f", latLong[0], latLong[1]); 1111 Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); 1112 activity.startActivity(mapsIntent); 1113 } 1114 } 1115 1116 /** 1117 * Dumps the stack trace. 1118 * 1119 * @param level How many levels of the stack are dumped. 0 means all. 1120 * @return A {@link java.lang.String} of all the output with newline between 1121 * each. 1122 */ 1123 public static String dumpStackTrace(int level) { 1124 StackTraceElement[] elems = Thread.currentThread().getStackTrace(); 1125 // Ignore the first 3 elements. 1126 level = (level == 0 ? elems.length : Math.min(level + 3, elems.length)); 1127 String ret = new String(); 1128 for (int i = 3; i < level; i++) { 1129 ret = ret + "\t" + elems[i].toString() + '\n'; 1130 } 1131 return ret; 1132 } 1133 1134 /** 1135 * Gets the theme color of a specific mode. 1136 * 1137 * @param modeIndex index of the mode 1138 * @param context current context 1139 * @return theme color of the mode if input index is valid, otherwise 0 1140 */ 1141 public static int getCameraThemeColorId(int modeIndex, Context context) { 1142 1143 // Find the theme color using id from the color array 1144 TypedArray colorRes = context.getResources() 1145 .obtainTypedArray(R.array.camera_mode_theme_color); 1146 if (modeIndex >= colorRes.length() || modeIndex < 0) { 1147 // Mode index not found 1148 Log.e(TAG, "Invalid mode index: " + modeIndex); 1149 return 0; 1150 } 1151 return colorRes.getResourceId(modeIndex, 0); 1152 } 1153 1154 /** 1155 * Gets the mode icon resource id of a specific mode. 1156 * 1157 * @param modeIndex index of the mode 1158 * @param context current context 1159 * @return icon resource id if the index is valid, otherwise 0 1160 */ 1161 public static int getCameraModeIconResId(int modeIndex, Context context) { 1162 // Find the camera mode icon using id 1163 TypedArray cameraModesIcons = context.getResources() 1164 .obtainTypedArray(R.array.camera_mode_icon); 1165 if (modeIndex >= cameraModesIcons.length() || modeIndex < 0) { 1166 // Mode index not found 1167 Log.e(TAG, "Invalid mode index: " + modeIndex); 1168 return 0; 1169 } 1170 return cameraModesIcons.getResourceId(modeIndex, 0); 1171 } 1172 1173 /** 1174 * Gets the mode text of a specific mode. 1175 * 1176 * @param modeIndex index of the mode 1177 * @param context current context 1178 * @return mode text if the index is valid, otherwise a new empty string 1179 */ 1180 public static String getCameraModeText(int modeIndex, Context context) { 1181 // Find the camera mode icon using id 1182 String[] cameraModesText = context.getResources() 1183 .getStringArray(R.array.camera_mode_text); 1184 if (modeIndex < 0 || modeIndex >= cameraModesText.length) { 1185 Log.e(TAG, "Invalid mode index: " + modeIndex); 1186 return new String(); 1187 } 1188 return cameraModesText[modeIndex]; 1189 } 1190 1191 /** 1192 * Gets the mode content description of a specific mode. 1193 * 1194 * @param modeIndex index of the mode 1195 * @param context current context 1196 * @return mode content description if the index is valid, otherwise a new 1197 * empty string 1198 */ 1199 public static String getCameraModeContentDescription(int modeIndex, Context context) { 1200 String[] cameraModesDesc = context.getResources() 1201 .getStringArray(R.array.camera_mode_content_description); 1202 if (modeIndex < 0 || modeIndex >= cameraModesDesc.length) { 1203 Log.e(TAG, "Invalid mode index: " + modeIndex); 1204 return new String(); 1205 } 1206 return cameraModesDesc[modeIndex]; 1207 } 1208 1209 /** 1210 * Gets the shutter icon res id for a specific mode. 1211 * 1212 * @param modeIndex index of the mode 1213 * @param context current context 1214 * @return mode shutter icon id if the index is valid, otherwise 0. 1215 */ 1216 public static int getCameraShutterIconId(int modeIndex, Context context) { 1217 // Find the camera mode icon using id 1218 TypedArray shutterIcons = context.getResources() 1219 .obtainTypedArray(R.array.camera_mode_shutter_icon); 1220 if (modeIndex < 0 || modeIndex >= shutterIcons.length()) { 1221 Log.e(TAG, "Invalid mode index: " + modeIndex); 1222 throw new IllegalStateException("Invalid mode index: " + modeIndex); 1223 } 1224 return shutterIcons.getResourceId(modeIndex, 0); 1225 } 1226 1227 /** 1228 * Gets the parent mode that hosts a specific mode in nav drawer. 1229 * 1230 * @param modeIndex index of the mode 1231 * @param context current context 1232 * @return mode id if the index is valid, otherwise 0 1233 */ 1234 public static int getCameraModeParentModeId(int modeIndex, Context context) { 1235 // Find the camera mode icon using id 1236 int[] cameraModeParent = context.getResources() 1237 .getIntArray(R.array.camera_mode_nested_in_nav_drawer); 1238 if (modeIndex < 0 || modeIndex >= cameraModeParent.length) { 1239 Log.e(TAG, "Invalid mode index: " + modeIndex); 1240 return 0; 1241 } 1242 return cameraModeParent[modeIndex]; 1243 } 1244 1245 /** 1246 * Gets the mode cover icon resource id of a specific mode. 1247 * 1248 * @param modeIndex index of the mode 1249 * @param context current context 1250 * @return icon resource id if the index is valid, otherwise 0 1251 */ 1252 public static int getCameraModeCoverIconResId(int modeIndex, Context context) { 1253 // Find the camera mode icon using id 1254 TypedArray cameraModesIcons = context.getResources() 1255 .obtainTypedArray(R.array.camera_mode_cover_icon); 1256 if (modeIndex >= cameraModesIcons.length() || modeIndex < 0) { 1257 // Mode index not found 1258 Log.e(TAG, "Invalid mode index: " + modeIndex); 1259 return 0; 1260 } 1261 return cameraModesIcons.getResourceId(modeIndex, 0); 1262 } 1263 1264 /** 1265 * Gets the number of cores available in this device, across all processors. 1266 * Requires: Ability to peruse the filesystem at "/sys/devices/system/cpu" 1267 * <p> 1268 * Source: http://stackoverflow.com/questions/7962155/ 1269 * 1270 * @return The number of cores, or 1 if failed to get result 1271 */ 1272 public static int getNumCpuCores() { 1273 // Private Class to display only CPU devices in the directory listing 1274 class CpuFilter implements java.io.FileFilter { 1275 @Override 1276 public boolean accept(java.io.File pathname) { 1277 // Check if filename is "cpu", followed by a single digit number 1278 if (java.util.regex.Pattern.matches("cpu[0-9]+", pathname.getName())) { 1279 return true; 1280 } 1281 return false; 1282 } 1283 } 1284 1285 try { 1286 // Get directory containing CPU info 1287 java.io.File dir = new java.io.File("/sys/devices/system/cpu/"); 1288 // Filter to only list the devices we care about 1289 java.io.File[] files = dir.listFiles(new CpuFilter()); 1290 // Return the number of cores (virtual CPU devices) 1291 return files.length; 1292 } catch (Exception e) { 1293 // Default to return 1 core 1294 Log.e(TAG, "Failed to count number of cores, defaulting to 1", e); 1295 return 1; 1296 } 1297 } 1298 1299 /** 1300 * Given the device orientation and Camera2 characteristics, this returns 1301 * the required JPEG rotation for this camera. 1302 * 1303 * @param deviceOrientationDegrees the device orientation in degrees. 1304 * @return The JPEG orientation in degrees. 1305 */ 1306 public static int getJpegRotation(int deviceOrientationDegrees, 1307 CameraCharacteristics characteristics) { 1308 if (deviceOrientationDegrees == OrientationEventListener.ORIENTATION_UNKNOWN) { 1309 return 0; 1310 } 1311 int facing = characteristics.get(CameraCharacteristics.LENS_FACING); 1312 int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); 1313 if (facing == CameraMetadata.LENS_FACING_FRONT) { 1314 return (sensorOrientation + deviceOrientationDegrees) % 360; 1315 } else { 1316 return (sensorOrientation - deviceOrientationDegrees + 360) % 360; 1317 } 1318 } 1319 } 1320