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