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