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