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(Activity context)453 public static int getDisplayRotation(Activity context) { 454 WindowManager windowManager = AndroidServices.instance().provideWindowManager(context); 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(Activity context)470 private static Size getDefaultDisplaySize(Activity context) { 471 WindowManager windowManager = AndroidServices.instance().provideWindowManager(context); 472 Point res = new Point(); 473 windowManager.getDefaultDisplay().getSize(res); 474 return new Size(res); 475 } 476 getOptimalPreviewSize(List<Size> sizes, double targetRatio, Activity context)477 public static Size getOptimalPreviewSize(List<Size> sizes, double targetRatio, 478 Activity context) { 479 int optimalPickIndex = getOptimalPreviewSizeIndex(sizes, targetRatio, context); 480 if (optimalPickIndex == -1) { 481 return null; 482 } else { 483 return sizes.get(optimalPickIndex); 484 } 485 } 486 487 /** 488 * Returns the index into 'sizes' that is most optimal given the current 489 * screen and target aspect ratio.. 490 * <p> 491 * This is using a default aspect ratio tolerance. If the tolerance is to be 492 * given you should call 493 * {@link #getOptimalPreviewSizeIndex(List, double, Double)} 494 * 495 * @param sizes the available preview sizes 496 * @param targetRatio the target aspect ratio, typically the aspect ratio of 497 * the picture size 498 * @param context the Activity to use for determining display information 499 * @return The index into 'previewSizes' for the optimal size, or -1, if no 500 * matching size was found. 501 */ getOptimalPreviewSizeIndex(List<Size> sizes, double targetRatio, Activity context)502 public static int getOptimalPreviewSizeIndex(List<Size> sizes, double targetRatio, 503 Activity context) { 504 // Use a very small tolerance because we want an exact match. HTC 4:3 505 // ratios is over .01 from true 4:3, so this value must be above .01, 506 // see b/18241645. 507 final double aspectRatioTolerance = 0.02; 508 509 return getOptimalPreviewSizeIndex(sizes, targetRatio, aspectRatioTolerance, context); 510 } 511 512 /** 513 * Returns the index into 'sizes' that is most optimal given the current 514 * screen, target aspect ratio and tolerance. 515 * 516 * @param previewSizes the available preview sizes 517 * @param targetRatio the target aspect ratio, typically the aspect ratio of 518 * the picture size 519 * @param aspectRatioTolerance the tolerance we allow between the selected 520 * preview size's aspect ratio and the target ratio. If this is 521 * set to 'null', the default value is used. 522 * @param context the Activity to use for determining display information 523 * @return The index into 'previewSizes' for the optimal size, or -1, if no 524 * matching size was found. 525 */ getOptimalPreviewSizeIndex( List<Size> previewSizes, double targetRatio, Double aspectRatioTolerance, Activity context)526 public static int getOptimalPreviewSizeIndex( 527 List<Size> previewSizes, double targetRatio, Double aspectRatioTolerance, 528 Activity context) { 529 if (previewSizes == null) { 530 return -1; 531 } 532 533 // If no particular aspect ratio tolerance is set, use the default 534 // value. 535 if (aspectRatioTolerance == null) { 536 return getOptimalPreviewSizeIndex(previewSizes, targetRatio, context); 537 } 538 539 int optimalSizeIndex = -1; 540 double minDiff = Double.MAX_VALUE; 541 542 // Because of bugs of overlay and layout, we sometimes will try to 543 // layout the viewfinder in the portrait orientation and thus get the 544 // wrong size of preview surface. When we change the preview size, the 545 // new overlay will be created before the old one closed, which causes 546 // an exception. For now, just get the screen size. 547 Size defaultDisplaySize = getDefaultDisplaySize(context); 548 int targetHeight = Math.min(defaultDisplaySize.getWidth(), defaultDisplaySize.getHeight()); 549 // Try to find an size match aspect ratio and size 550 for (int i = 0; i < previewSizes.size(); i++) { 551 Size size = previewSizes.get(i); 552 double ratio = (double) size.getWidth() / size.getHeight(); 553 if (Math.abs(ratio - targetRatio) > aspectRatioTolerance) { 554 continue; 555 } 556 557 double heightDiff = Math.abs(size.getHeight() - targetHeight); 558 if (heightDiff < minDiff) { 559 optimalSizeIndex = i; 560 minDiff = heightDiff; 561 } else if (heightDiff == minDiff) { 562 // Prefer resolutions smaller-than-display when an equally close 563 // larger-than-display resolution is available 564 if (size.getHeight() < targetHeight) { 565 optimalSizeIndex = i; 566 minDiff = heightDiff; 567 } 568 } 569 } 570 // Cannot find the one match the aspect ratio. This should not happen. 571 // Ignore the requirement. 572 if (optimalSizeIndex == -1) { 573 Log.w(TAG, "No preview size match the aspect ratio. available sizes: " + previewSizes); 574 minDiff = Double.MAX_VALUE; 575 for (int i = 0; i < previewSizes.size(); i++) { 576 Size size = previewSizes.get(i); 577 if (Math.abs(size.getHeight() - targetHeight) < minDiff) { 578 optimalSizeIndex = i; 579 minDiff = Math.abs(size.getHeight() - targetHeight); 580 } 581 } 582 } 583 584 return optimalSizeIndex; 585 } 586 587 /** 588 * Returns the largest picture size which matches the given aspect ratio, 589 * except for the special WYSIWYG case where the picture size exactly 590 * matches the target size. 591 * 592 * @param sizes a list of candidate sizes, available for use 593 * @param targetWidth the ideal width of the video snapshot 594 * @param targetHeight the ideal height of the video snapshot 595 * @return the Optimal Video Snapshot Picture Size 596 */ getOptimalVideoSnapshotPictureSize( List<Size> sizes, int targetWidth, int targetHeight)597 public static Size getOptimalVideoSnapshotPictureSize( 598 List<Size> sizes, int targetWidth, 599 int targetHeight) { 600 601 // Use a very small tolerance because we want an exact match. 602 final double ASPECT_TOLERANCE = 0.001; 603 if (sizes == null) { 604 return null; 605 } 606 607 Size optimalSize = null; 608 609 // WYSIWYG Override 610 // We assume that physical display constraints have already been 611 // imposed on the variables sizes 612 for (Size size : sizes) { 613 if (size.height() == targetHeight && size.width() == targetWidth) { 614 return size; 615 } 616 } 617 618 // Try to find a size matches aspect ratio and has the largest width 619 final double targetRatio = (double) targetWidth / targetHeight; 620 for (Size size : sizes) { 621 double ratio = (double) size.width() / size.height(); 622 if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) { 623 continue; 624 } 625 if (optimalSize == null || size.width() > optimalSize.width()) { 626 optimalSize = size; 627 } 628 } 629 630 // Cannot find one that matches the aspect ratio. This should not 631 // happen. Ignore the requirement. 632 if (optimalSize == null) { 633 Log.w(TAG, "No picture size match the aspect ratio"); 634 for (Size size : sizes) { 635 if (optimalSize == null || size.width() > optimalSize.width()) { 636 optimalSize = size; 637 } 638 } 639 } 640 return optimalSize; 641 } 642 643 // This is for test only. Allow the camera to launch the specific camera. getCameraFacingIntentExtras(Activity currentActivity)644 public static int getCameraFacingIntentExtras(Activity currentActivity) { 645 int cameraId = -1; 646 647 int intentCameraId = 648 currentActivity.getIntent().getIntExtra(CameraUtil.EXTRAS_CAMERA_FACING, -1); 649 650 if (isFrontCameraIntent(intentCameraId)) { 651 // Check if the front camera exist 652 int frontCameraId = ((CameraActivity) currentActivity).getCameraProvider() 653 .getFirstFrontCameraId(); 654 if (frontCameraId != -1) { 655 cameraId = frontCameraId; 656 } 657 } else if (isBackCameraIntent(intentCameraId)) { 658 // Check if the back camera exist 659 int backCameraId = ((CameraActivity) currentActivity).getCameraProvider() 660 .getFirstBackCameraId(); 661 if (backCameraId != -1) { 662 cameraId = backCameraId; 663 } 664 } 665 return cameraId; 666 } 667 isFrontCameraIntent(int intentCameraId)668 private static boolean isFrontCameraIntent(int intentCameraId) { 669 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT); 670 } 671 isBackCameraIntent(int intentCameraId)672 private static boolean isBackCameraIntent(int intentCameraId) { 673 return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK); 674 } 675 676 private static int sLocation[] = new int[2]; 677 678 // This method is not thread-safe. pointInView(float x, float y, View v)679 public static boolean pointInView(float x, float y, View v) { 680 v.getLocationInWindow(sLocation); 681 return x >= sLocation[0] && x < (sLocation[0] + v.getWidth()) 682 && y >= sLocation[1] && y < (sLocation[1] + v.getHeight()); 683 } 684 getRelativeLocation(View reference, View view)685 public static int[] getRelativeLocation(View reference, View view) { 686 reference.getLocationInWindow(sLocation); 687 int referenceX = sLocation[0]; 688 int referenceY = sLocation[1]; 689 view.getLocationInWindow(sLocation); 690 sLocation[0] -= referenceX; 691 sLocation[1] -= referenceY; 692 return sLocation; 693 } 694 isUriValid(Uri uri, ContentResolver resolver)695 public static boolean isUriValid(Uri uri, ContentResolver resolver) { 696 if (uri == null) { 697 return false; 698 } 699 700 try { 701 ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r"); 702 if (pfd == null) { 703 Log.e(TAG, "Fail to open URI. URI=" + uri); 704 return false; 705 } 706 pfd.close(); 707 } catch (IOException ex) { 708 return false; 709 } 710 return true; 711 } 712 dumpRect(RectF rect, String msg)713 public static void dumpRect(RectF rect, String msg) { 714 Log.v(TAG, msg + "=(" + rect.left + "," + rect.top 715 + "," + rect.right + "," + rect.bottom + ")"); 716 } 717 inlineRectToRectF(RectF rectF, Rect rect)718 public static void inlineRectToRectF(RectF rectF, Rect rect) { 719 rect.left = Math.round(rectF.left); 720 rect.top = Math.round(rectF.top); 721 rect.right = Math.round(rectF.right); 722 rect.bottom = Math.round(rectF.bottom); 723 } 724 rectFToRect(RectF rectF)725 public static Rect rectFToRect(RectF rectF) { 726 Rect rect = new Rect(); 727 inlineRectToRectF(rectF, rect); 728 return rect; 729 } 730 rectToRectF(Rect r)731 public static RectF rectToRectF(Rect r) { 732 return new RectF(r.left, r.top, r.right, r.bottom); 733 } 734 prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation, int viewWidth, int viewHeight)735 public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation, 736 int viewWidth, int viewHeight) { 737 // Need mirror for front camera. 738 matrix.setScale(mirror ? -1 : 1, 1); 739 // This is the value for android.hardware.Camera.setDisplayOrientation. 740 matrix.postRotate(displayOrientation); 741 // Camera driver coordinates range from (-1000, -1000) to (1000, 1000). 742 // UI coordinates range from (0, 0) to (width, height). 743 matrix.postScale(viewWidth / 2000f, viewHeight / 2000f); 744 matrix.postTranslate(viewWidth / 2f, viewHeight / 2f); 745 } 746 createJpegName(long dateTaken)747 public String createJpegName(long dateTaken) { 748 synchronized (mImageFileNamer) { 749 return mImageFileNamer.generateName(dateTaken); 750 } 751 } 752 broadcastNewPicture(Context context, Uri uri)753 public static void broadcastNewPicture(Context context, Uri uri) { 754 context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri)); 755 // Keep compatibility 756 context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri)); 757 } 758 fadeIn(View view, float startAlpha, float endAlpha, long duration)759 public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) { 760 if (view.getVisibility() == View.VISIBLE) { 761 return; 762 } 763 764 view.setVisibility(View.VISIBLE); 765 Animation animation = new AlphaAnimation(startAlpha, endAlpha); 766 animation.setDuration(duration); 767 view.startAnimation(animation); 768 } 769 setGpsParameters(CameraSettings settings, Location loc)770 public static void setGpsParameters(CameraSettings settings, Location loc) { 771 // Clear previous GPS location from the parameters. 772 settings.clearGpsData(); 773 774 boolean hasLatLon = false; 775 double lat; 776 double lon; 777 // Set GPS location. 778 if (loc != null) { 779 lat = loc.getLatitude(); 780 lon = loc.getLongitude(); 781 hasLatLon = (lat != 0.0d) || (lon != 0.0d); 782 } 783 784 if (!hasLatLon) { 785 // We always encode GpsTimeStamp even if the GPS location is not 786 // available. 787 settings.setGpsData( 788 new CameraSettings.GpsData(0f, 0f, 0f, System.currentTimeMillis() / 1000, null) 789 ); 790 } else { 791 Log.d(TAG, "Set gps location"); 792 // for NETWORK_PROVIDER location provider, we may have 793 // no altitude information, but the driver needs it, so 794 // we fake one. 795 // Location.getTime() is UTC in milliseconds. 796 // gps-timestamp is UTC in seconds. 797 long utcTimeSeconds = loc.getTime() / 1000; 798 settings.setGpsData(new CameraSettings.GpsData(loc.getLatitude(), loc.getLongitude(), 799 (loc.hasAltitude() ? loc.getAltitude() : 0), 800 (utcTimeSeconds != 0 ? utcTimeSeconds : System.currentTimeMillis()), 801 loc.getProvider().toUpperCase())); 802 } 803 } 804 805 /** 806 * For still image capture, we need to get the right fps range such that the 807 * camera can slow down the framerate to allow for less-noisy/dark 808 * viewfinder output in dark conditions. 809 * 810 * @param capabilities Camera's capabilities. 811 * @return null if no appropiate fps range can't be found. Otherwise, return 812 * the right range. 813 */ getPhotoPreviewFpsRange(CameraCapabilities capabilities)814 public static int[] getPhotoPreviewFpsRange(CameraCapabilities capabilities) { 815 return getPhotoPreviewFpsRange(capabilities.getSupportedPreviewFpsRange()); 816 } 817 getPhotoPreviewFpsRange(List<int[]> frameRates)818 public static int[] getPhotoPreviewFpsRange(List<int[]> frameRates) { 819 if (frameRates.size() == 0) { 820 Log.e(TAG, "No suppoted frame rates returned!"); 821 return null; 822 } 823 824 // Find the lowest min rate in supported ranges who can cover 30fps. 825 int lowestMinRate = MAX_PREVIEW_FPS_TIMES_1000; 826 for (int[] rate : frameRates) { 827 int minFps = rate[0]; 828 int maxFps = rate[1]; 829 if (maxFps >= PREFERRED_PREVIEW_FPS_TIMES_1000 && 830 minFps <= PREFERRED_PREVIEW_FPS_TIMES_1000 && 831 minFps < lowestMinRate) { 832 lowestMinRate = minFps; 833 } 834 } 835 836 // Find all the modes with the lowest min rate found above, the pick the 837 // one with highest max rate. 838 int resultIndex = -1; 839 int highestMaxRate = 0; 840 for (int i = 0; i < frameRates.size(); i++) { 841 int[] rate = frameRates.get(i); 842 int minFps = rate[0]; 843 int maxFps = rate[1]; 844 if (minFps == lowestMinRate && highestMaxRate < maxFps) { 845 highestMaxRate = maxFps; 846 resultIndex = i; 847 } 848 } 849 850 if (resultIndex >= 0) { 851 return frameRates.get(resultIndex); 852 } 853 Log.e(TAG, "Can't find an appropiate frame rate range!"); 854 return null; 855 } 856 getMaxPreviewFpsRange(List<int[]> frameRates)857 public static int[] getMaxPreviewFpsRange(List<int[]> frameRates) { 858 if (frameRates != null && frameRates.size() > 0) { 859 // The list is sorted. Return the last element. 860 return frameRates.get(frameRates.size() - 1); 861 } 862 return new int[0]; 863 } 864 throwIfCameraDisabled()865 public static void throwIfCameraDisabled() throws CameraDisabledException { 866 // Check if device policy has disabled the camera. 867 DevicePolicyManager dpm = AndroidServices.instance().provideDevicePolicyManager(); 868 if (dpm.getCameraDisabled(null)) { 869 throw new CameraDisabledException(); 870 } 871 } 872 873 /** 874 * Generates a 1d Gaussian mask of the input array size, and store the mask 875 * in the input array. 876 * 877 * @param mask empty array of size n, where n will be used as the size of 878 * the Gaussian mask, and the array will be populated with the 879 * values of the mask. 880 */ getGaussianMask(float[] mask)881 private static void getGaussianMask(float[] mask) { 882 int len = mask.length; 883 int mid = len / 2; 884 float sigma = len; 885 float sum = 0; 886 for (int i = 0; i <= mid; i++) { 887 float ex = (float) Math.exp(-(i - mid) * (i - mid) / (mid * mid)) 888 / (2 * sigma * sigma); 889 int symmetricIndex = len - 1 - i; 890 mask[i] = ex; 891 mask[symmetricIndex] = ex; 892 sum += mask[i]; 893 if (i != symmetricIndex) { 894 sum += mask[symmetricIndex]; 895 } 896 } 897 898 for (int i = 0; i < mask.length; i++) { 899 mask[i] /= sum; 900 } 901 902 } 903 904 /** 905 * Add two pixels together where the second pixel will be applied with a 906 * weight. 907 * 908 * @param pixel pixel color value of weight 1 909 * @param newPixel second pixel color value where the weight will be applied 910 * @param weight a float weight that will be applied to the second pixel 911 * color 912 * @return the weighted addition of the two pixels 913 */ addPixel(int pixel, int newPixel, float weight)914 public static int addPixel(int pixel, int newPixel, float weight) { 915 // TODO: scale weight to [0, 1024] to avoid casting to float and back to 916 // int. 917 int r = ((pixel & 0x00ff0000) + (int) ((newPixel & 0x00ff0000) * weight)) & 0x00ff0000; 918 int g = ((pixel & 0x0000ff00) + (int) ((newPixel & 0x0000ff00) * weight)) & 0x0000ff00; 919 int b = ((pixel & 0x000000ff) + (int) ((newPixel & 0x000000ff) * weight)) & 0x000000ff; 920 return 0xff000000 | r | g | b; 921 } 922 923 /** 924 * Apply blur to the input image represented in an array of colors and put 925 * the output image, in the form of an array of colors, into the output 926 * array. 927 * 928 * @param src source array of colors 929 * @param out output array of colors after the blur 930 * @param w width of the image 931 * @param h height of the image 932 * @param size size of the Gaussian blur mask 933 */ blur(int[] src, int[] out, int w, int h, int size)934 public static void blur(int[] src, int[] out, int w, int h, int size) { 935 float[] k = new float[size]; 936 int off = size / 2; 937 938 getGaussianMask(k); 939 940 int[] tmp = new int[src.length]; 941 942 // Apply the 1d Gaussian mask horizontally to the image and put the 943 // intermediat results in a temporary array. 944 int rowPointer = 0; 945 for (int y = 0; y < h; y++) { 946 for (int x = 0; x < w; x++) { 947 int sum = 0; 948 for (int i = 0; i < k.length; i++) { 949 int dx = x + i - off; 950 dx = clamp(dx, 0, w - 1); 951 sum = addPixel(sum, src[rowPointer + dx], k[i]); 952 } 953 tmp[x + rowPointer] = sum; 954 } 955 rowPointer += w; 956 } 957 958 // Apply the 1d Gaussian mask vertically to the intermediate array, and 959 // the final results will be stored in the output array. 960 for (int x = 0; x < w; x++) { 961 rowPointer = 0; 962 for (int y = 0; y < h; y++) { 963 int sum = 0; 964 for (int i = 0; i < k.length; i++) { 965 int dy = y + i - off; 966 dy = clamp(dy, 0, h - 1); 967 sum = addPixel(sum, tmp[dy * w + x], k[i]); 968 } 969 out[x + rowPointer] = sum; 970 rowPointer += w; 971 } 972 } 973 } 974 975 /** 976 * Calculates a new dimension to fill the bound with the original aspect 977 * ratio preserved. 978 * 979 * @param imageWidth The original width. 980 * @param imageHeight The original height. 981 * @param imageRotation The clockwise rotation in degrees of the image which 982 * the original dimension comes from. 983 * @param boundWidth The width of the bound. 984 * @param boundHeight The height of the bound. 985 * @returns The final width/height stored in Point.x/Point.y to fill the 986 * bounds and preserve image aspect ratio. 987 */ resizeToFill(int imageWidth, int imageHeight, int imageRotation, int boundWidth, int boundHeight)988 public static Point resizeToFill(int imageWidth, int imageHeight, int imageRotation, 989 int boundWidth, int boundHeight) { 990 if (imageRotation % 180 != 0) { 991 // Swap width and height. 992 int savedWidth = imageWidth; 993 imageWidth = imageHeight; 994 imageHeight = savedWidth; 995 } 996 997 Point p = new Point(); 998 p.x = boundWidth; 999 p.y = boundHeight; 1000 1001 // In some cases like automated testing, image height/width may not be 1002 // loaded, to avoid divide by zero fall back to provided bounds. 1003 if (imageWidth != 0 && imageHeight != 0) { 1004 if (imageWidth * boundHeight > boundWidth * imageHeight) { 1005 p.y = imageHeight * p.x / imageWidth; 1006 } else { 1007 p.x = imageWidth * p.y / imageHeight; 1008 } 1009 } else { 1010 Log.w(TAG, "zero width/height, falling back to bounds (w|h|bw|bh):" 1011 + imageWidth + "|" + imageHeight + "|" + boundWidth + "|" 1012 + boundHeight); 1013 } 1014 1015 return p; 1016 } 1017 1018 private static class ImageFileNamer { 1019 private final SimpleDateFormat mFormat; 1020 1021 // The date (in milliseconds) used to generate the last name. 1022 private long mLastDate; 1023 1024 // Number of names generated for the same second. 1025 private int mSameSecondCount; 1026 ImageFileNamer(String format)1027 public ImageFileNamer(String format) { 1028 mFormat = new SimpleDateFormat(format); 1029 } 1030 generateName(long dateTaken)1031 public String generateName(long dateTaken) { 1032 Date date = new Date(dateTaken); 1033 String result = mFormat.format(date); 1034 1035 // If the last name was generated for the same second, 1036 // we append _1, _2, etc to the name. 1037 if (dateTaken / 1000 == mLastDate / 1000) { 1038 mSameSecondCount++; 1039 result += "_" + mSameSecondCount; 1040 } else { 1041 mLastDate = dateTaken; 1042 mSameSecondCount = 0; 1043 } 1044 1045 return result; 1046 } 1047 } 1048 playVideo(CameraActivity activity, Uri uri, String title)1049 public static void playVideo(CameraActivity activity, Uri uri, String title) { 1050 try { 1051 boolean isSecureCamera = activity.isSecureCamera(); 1052 if (!isSecureCamera) { 1053 Intent intent = IntentHelper.getVideoPlayerIntent(uri) 1054 .putExtra(Intent.EXTRA_TITLE, title) 1055 .putExtra(KEY_TREAT_UP_AS_BACK, true); 1056 activity.launchActivityByIntent(intent); 1057 } else { 1058 // In order not to send out any intent to be intercepted and 1059 // show the lock screen immediately, we just let the secure 1060 // camera activity finish. 1061 activity.finish(); 1062 } 1063 } catch (ActivityNotFoundException e) { 1064 Toast.makeText(activity, activity.getString(R.string.video_err), 1065 Toast.LENGTH_SHORT).show(); 1066 } 1067 } 1068 1069 /** 1070 * Starts GMM with the given location shown. If this fails, and GMM could 1071 * not be found, we use a geo intent as a fallback. 1072 * 1073 * @param activity the activity to use for launching the Maps intent. 1074 * @param latLong a 2-element array containing {latitude/longitude}. 1075 */ showOnMap(Activity activity, double[] latLong)1076 public static void showOnMap(Activity activity, double[] latLong) { 1077 try { 1078 // We don't use "geo:latitude,longitude" because it only centers 1079 // the MapView to the specified location, but we need a marker 1080 // for further operations (routing to/from). 1081 // The q=(lat, lng) syntax is suggested by geo-team. 1082 String uri = String.format(Locale.ENGLISH, "http://maps.google.com/maps?f=q&q=(%f,%f)", 1083 latLong[0], latLong[1]); 1084 ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME, 1085 MAPS_CLASS_NAME); 1086 Intent mapsIntent = new Intent(Intent.ACTION_VIEW, 1087 Uri.parse(uri)).setComponent(compName); 1088 mapsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); 1089 activity.startActivity(mapsIntent); 1090 } catch (ActivityNotFoundException e) { 1091 // Use the "geo intent" if no GMM is installed 1092 Log.e(TAG, "GMM activity not found!", e); 1093 String url = String.format(Locale.ENGLISH, "geo:%f,%f", latLong[0], latLong[1]); 1094 Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); 1095 activity.startActivity(mapsIntent); 1096 } 1097 } 1098 1099 /** 1100 * Dumps the stack trace. 1101 * 1102 * @param level How many levels of the stack are dumped. 0 means all. 1103 * @return A {@link java.lang.String} of all the output with newline between 1104 * each. 1105 */ dumpStackTrace(int level)1106 public static String dumpStackTrace(int level) { 1107 StackTraceElement[] elems = Thread.currentThread().getStackTrace(); 1108 // Ignore the first 3 elements. 1109 level = (level == 0 ? elems.length : Math.min(level + 3, elems.length)); 1110 String ret = new String(); 1111 for (int i = 3; i < level; i++) { 1112 ret = ret + "\t" + elems[i].toString() + '\n'; 1113 } 1114 return ret; 1115 } 1116 1117 /** 1118 * Gets the theme color of a specific mode. 1119 * 1120 * @param modeIndex index of the mode 1121 * @param context current context 1122 * @return theme color of the mode if input index is valid, otherwise 0 1123 */ getCameraThemeColorId(int modeIndex, Context context)1124 public static int getCameraThemeColorId(int modeIndex, Context context) { 1125 1126 // Find the theme color using id from the color array 1127 TypedArray colorRes = context.getResources() 1128 .obtainTypedArray(R.array.camera_mode_theme_color); 1129 if (modeIndex >= colorRes.length() || modeIndex < 0) { 1130 // Mode index not found 1131 Log.e(TAG, "Invalid mode index: " + modeIndex); 1132 return 0; 1133 } 1134 return colorRes.getResourceId(modeIndex, 0); 1135 } 1136 1137 /** 1138 * Gets the mode icon resource id of a specific mode. 1139 * 1140 * @param modeIndex index of the mode 1141 * @param context current context 1142 * @return icon resource id if the index is valid, otherwise 0 1143 */ getCameraModeIconResId(int modeIndex, Context context)1144 public static int getCameraModeIconResId(int modeIndex, Context context) { 1145 // Find the camera mode icon using id 1146 TypedArray cameraModesIcons = context.getResources() 1147 .obtainTypedArray(R.array.camera_mode_icon); 1148 if (modeIndex >= cameraModesIcons.length() || modeIndex < 0) { 1149 // Mode index not found 1150 Log.e(TAG, "Invalid mode index: " + modeIndex); 1151 return 0; 1152 } 1153 return cameraModesIcons.getResourceId(modeIndex, 0); 1154 } 1155 1156 /** 1157 * Gets the mode text of a specific mode. 1158 * 1159 * @param modeIndex index of the mode 1160 * @param context current context 1161 * @return mode text if the index is valid, otherwise a new empty string 1162 */ getCameraModeText(int modeIndex, Context context)1163 public static String getCameraModeText(int modeIndex, Context context) { 1164 // Find the camera mode icon using id 1165 String[] cameraModesText = context.getResources() 1166 .getStringArray(R.array.camera_mode_text); 1167 if (modeIndex < 0 || modeIndex >= cameraModesText.length) { 1168 Log.e(TAG, "Invalid mode index: " + modeIndex); 1169 return new String(); 1170 } 1171 return cameraModesText[modeIndex]; 1172 } 1173 1174 /** 1175 * Gets the mode content description of a specific mode. 1176 * 1177 * @param modeIndex index of the mode 1178 * @param context current context 1179 * @return mode content description if the index is valid, otherwise a new 1180 * empty string 1181 */ getCameraModeContentDescription(int modeIndex, Context context)1182 public static String getCameraModeContentDescription(int modeIndex, Context context) { 1183 String[] cameraModesDesc = context.getResources() 1184 .getStringArray(R.array.camera_mode_content_description); 1185 if (modeIndex < 0 || modeIndex >= cameraModesDesc.length) { 1186 Log.e(TAG, "Invalid mode index: " + modeIndex); 1187 return new String(); 1188 } 1189 return cameraModesDesc[modeIndex]; 1190 } 1191 1192 /** 1193 * Gets the shutter icon res id for a specific mode. 1194 * 1195 * @param modeIndex index of the mode 1196 * @param context current context 1197 * @return mode shutter icon id if the index is valid, otherwise 0. 1198 */ getCameraShutterIconId(int modeIndex, Context context)1199 public static int getCameraShutterIconId(int modeIndex, Context context) { 1200 // Find the camera mode icon using id 1201 TypedArray shutterIcons = context.getResources() 1202 .obtainTypedArray(R.array.camera_mode_shutter_icon); 1203 if (modeIndex < 0 || modeIndex >= shutterIcons.length()) { 1204 Log.e(TAG, "Invalid mode index: " + modeIndex); 1205 throw new IllegalStateException("Invalid mode index: " + modeIndex); 1206 } 1207 return shutterIcons.getResourceId(modeIndex, 0); 1208 } 1209 1210 /** 1211 * Gets the parent mode that hosts a specific mode in nav drawer. 1212 * 1213 * @param modeIndex index of the mode 1214 * @param context current context 1215 * @return mode id if the index is valid, otherwise 0 1216 */ getCameraModeParentModeId(int modeIndex, Context context)1217 public static int getCameraModeParentModeId(int modeIndex, Context context) { 1218 // Find the camera mode icon using id 1219 int[] cameraModeParent = context.getResources() 1220 .getIntArray(R.array.camera_mode_nested_in_nav_drawer); 1221 if (modeIndex < 0 || modeIndex >= cameraModeParent.length) { 1222 Log.e(TAG, "Invalid mode index: " + modeIndex); 1223 return 0; 1224 } 1225 return cameraModeParent[modeIndex]; 1226 } 1227 1228 /** 1229 * Gets the mode cover icon resource id of a specific mode. 1230 * 1231 * @param modeIndex index of the mode 1232 * @param context current context 1233 * @return icon resource id if the index is valid, otherwise 0 1234 */ getCameraModeCoverIconResId(int modeIndex, Context context)1235 public static int getCameraModeCoverIconResId(int modeIndex, Context context) { 1236 // Find the camera mode icon using id 1237 TypedArray cameraModesIcons = context.getResources() 1238 .obtainTypedArray(R.array.camera_mode_cover_icon); 1239 if (modeIndex >= cameraModesIcons.length() || modeIndex < 0) { 1240 // Mode index not found 1241 Log.e(TAG, "Invalid mode index: " + modeIndex); 1242 return 0; 1243 } 1244 return cameraModesIcons.getResourceId(modeIndex, 0); 1245 } 1246 1247 /** 1248 * Gets the number of cores available in this device, across all processors. 1249 * Requires: Ability to peruse the filesystem at "/sys/devices/system/cpu" 1250 * <p> 1251 * Source: http://stackoverflow.com/questions/7962155/ 1252 * 1253 * @return The number of cores, or 1 if failed to get result 1254 */ getNumCpuCores()1255 public static int getNumCpuCores() { 1256 // Private Class to display only CPU devices in the directory listing 1257 class CpuFilter implements java.io.FileFilter { 1258 @Override 1259 public boolean accept(java.io.File pathname) { 1260 // Check if filename is "cpu", followed by a single digit number 1261 if (java.util.regex.Pattern.matches("cpu[0-9]+", pathname.getName())) { 1262 return true; 1263 } 1264 return false; 1265 } 1266 } 1267 1268 try { 1269 // Get directory containing CPU info 1270 java.io.File dir = new java.io.File("/sys/devices/system/cpu/"); 1271 // Filter to only list the devices we care about 1272 java.io.File[] files = dir.listFiles(new CpuFilter()); 1273 // Return the number of cores (virtual CPU devices) 1274 return files.length; 1275 } catch (Exception e) { 1276 // Default to return 1 core 1277 Log.e(TAG, "Failed to count number of cores, defaulting to 1", e); 1278 return 1; 1279 } 1280 } 1281 1282 /** 1283 * Given the device orientation and Camera2 characteristics, this returns 1284 * the required JPEG rotation for this camera. 1285 * 1286 * @param deviceOrientationDegrees the clockwise angle of the device orientation from its 1287 * natural orientation in degrees. 1288 * @return The angle to rotate image clockwise in degrees. It should be 0, 90, 180, or 270. 1289 */ getJpegRotation(int deviceOrientationDegrees, CameraCharacteristics characteristics)1290 public static int getJpegRotation(int deviceOrientationDegrees, 1291 CameraCharacteristics characteristics) { 1292 if (deviceOrientationDegrees == OrientationEventListener.ORIENTATION_UNKNOWN) { 1293 return 0; 1294 } 1295 boolean isFrontCamera = characteristics.get(CameraCharacteristics.LENS_FACING) == 1296 CameraMetadata.LENS_FACING_FRONT; 1297 int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); 1298 return getImageRotation(sensorOrientation, deviceOrientationDegrees, isFrontCamera); 1299 } 1300 1301 /** 1302 * Given the camera sensor orientation and device orientation, this returns a clockwise angle 1303 * which the final image needs to be rotated to be upright on the device screen. 1304 * 1305 * @param sensorOrientation Clockwise angle through which the output image needs to be rotated 1306 * to be upright on the device screen in its native orientation. 1307 * @param deviceOrientation Clockwise angle of the device orientation from its 1308 * native orientation when front camera faces user. 1309 * @param isFrontCamera True if the camera is front-facing. 1310 * @return The angle to rotate image clockwise in degrees. It should be 0, 90, 180, or 270. 1311 */ getImageRotation(int sensorOrientation, int deviceOrientation, boolean isFrontCamera)1312 public static int getImageRotation(int sensorOrientation, 1313 int deviceOrientation, 1314 boolean isFrontCamera) { 1315 // The sensor of front camera faces in the opposite direction from back camera. 1316 if (isFrontCamera) { 1317 deviceOrientation = (360 - deviceOrientation) % 360; 1318 } 1319 return (sensorOrientation + deviceOrientation) % 360; 1320 } 1321 } 1322