• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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