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