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