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.graphics.Bitmap;
     29 import android.graphics.BitmapFactory;
     30 import android.graphics.Matrix;
     31 import android.graphics.Point;
     32 import android.graphics.Rect;
     33 import android.graphics.RectF;
     34 import android.hardware.Camera;
     35 import android.hardware.Camera.CameraInfo;
     36 import android.hardware.Camera.Parameters;
     37 import android.hardware.Camera.Size;
     38 import android.location.Location;
     39 import android.net.Uri;
     40 import android.os.Handler;
     41 import android.os.ParcelFileDescriptor;
     42 import android.telephony.TelephonyManager;
     43 import android.util.DisplayMetrics;
     44 import android.util.Log;
     45 import android.util.TypedValue;
     46 import android.view.Display;
     47 import android.view.OrientationEventListener;
     48 import android.view.Surface;
     49 import android.view.View;
     50 import android.view.WindowManager;
     51 import android.view.animation.AlphaAnimation;
     52 import android.view.animation.Animation;
     53 import android.widget.Toast;
     54 
     55 import com.android.camera.CameraActivity;
     56 import com.android.camera.CameraDisabledException;
     57 import com.android.camera.CameraHolder;
     58 import com.android.camera.CameraManager;
     59 import com.android.camera.util.IntentHelper;
     60 import com.android.camera2.R;
     61 
     62 import java.io.Closeable;
     63 import java.io.IOException;
     64 import java.lang.reflect.Method;
     65 import java.text.SimpleDateFormat;
     66 import java.util.Date;
     67 import java.util.List;
     68 import java.util.Locale;
     69 import java.util.StringTokenizer;
     70 
     71 /**
     72  * Collection of utility functions used in this package.
     73  */
     74 public class CameraUtil {
     75     private static final String TAG = "Util";
     76 
     77     // For calculate the best fps range for still image capture.
     78     private final static int MAX_PREVIEW_FPS_TIMES_1000 = 400000;
     79     private final static int PREFERRED_PREVIEW_FPS_TIMES_1000 = 30000;
     80 
     81     // For creating crop intents.
     82     public static final String KEY_RETURN_DATA = "return-data";
     83     public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked";
     84 
     85     // Orientation hysteresis amount used in rounding, in degrees
     86     public static final int ORIENTATION_HYSTERESIS = 5;
     87 
     88     public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW";
     89     // See android.hardware.Camera.ACTION_NEW_PICTURE.
     90     public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
     91     // See android.hardware.Camera.ACTION_NEW_VIDEO.
     92     public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
     93 
     94     // Broadcast Action: The camera application has become active in picture-taking mode.
     95     public static final String ACTION_CAMERA_STARTED = "com.android.camera.action.CAMERA_STARTED";
     96     // Broadcast Action: The camera application is no longer in active picture-taking mode.
     97     public static final String ACTION_CAMERA_STOPPED = "com.android.camera.action.CAMERA_STOPPED";
     98     // When the camera application is active in picture-taking mode, it listens for this intent,
     99     // which upon receipt will trigger the shutter to capture a new picture, as if the user had
    100     // pressed the shutter button.
    101     public static final String ACTION_CAMERA_SHUTTER_CLICK =
    102             "com.android.camera.action.SHUTTER_CLICK";
    103 
    104     // Fields from android.hardware.Camera.Parameters
    105     public static final String FOCUS_MODE_CONTINUOUS_PICTURE = "continuous-picture";
    106     public static final String RECORDING_HINT = "recording-hint";
    107     private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
    108     private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported";
    109     private static final String VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported";
    110     public static final String SCENE_MODE_HDR = "hdr";
    111     public static final String TRUE = "true";
    112     public static final String FALSE = "false";
    113 
    114     // Fields for the show-on-maps-functionality
    115     private static final String MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
    116     private static final String MAPS_CLASS_NAME = "com.google.android.maps.MapsActivity";
    117 
    118     /** Has to be in sync with the receiving MovieActivity. */
    119     public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back";
    120 
    121     public static boolean isSupported(String value, List<String> supported) {
    122         return supported == null ? false : supported.indexOf(value) >= 0;
    123     }
    124 
    125     public static boolean isAutoExposureLockSupported(Parameters params) {
    126         return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
    127     }
    128 
    129     public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
    130         return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
    131     }
    132 
    133     public static boolean isVideoSnapshotSupported(Parameters params) {
    134         return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED));
    135     }
    136 
    137     public static boolean isCameraHdrSupported(Parameters params) {
    138         List<String> supported = params.getSupportedSceneModes();
    139         return (supported != null) && supported.contains(SCENE_MODE_HDR);
    140     }
    141 
    142     public static boolean isMeteringAreaSupported(Parameters params) {
    143         return params.getMaxNumMeteringAreas() > 0;
    144     }
    145 
    146     public static boolean isFocusAreaSupported(Parameters params) {
    147         return (params.getMaxNumFocusAreas() > 0
    148                 && isSupported(Parameters.FOCUS_MODE_AUTO,
    149                         params.getSupportedFocusModes()));
    150     }
    151 
    152     // Private intent extras. Test only.
    153     private static final String EXTRAS_CAMERA_FACING =
    154             "android.intent.extras.CAMERA_FACING";
    155 
    156     private static float sPixelDensity = 1;
    157     private static ImageFileNamer sImageFileNamer;
    158 
    159     private CameraUtil() {
    160     }
    161 
    162     public static void initialize(Context context) {
    163         DisplayMetrics metrics = new DisplayMetrics();
    164         WindowManager wm = (WindowManager)
    165                 context.getSystemService(Context.WINDOW_SERVICE);
    166         wm.getDefaultDisplay().getMetrics(metrics);
    167         sPixelDensity = metrics.density;
    168         sImageFileNamer = new ImageFileNamer(
    169                 context.getString(R.string.image_file_name_format));
    170     }
    171 
    172     public static int dpToPixel(int dp) {
    173         return Math.round(sPixelDensity * dp);
    174     }
    175 
    176     // Rotates the bitmap by the specified degree.
    177     // If a new bitmap is created, the original bitmap is recycled.
    178     public static Bitmap rotate(Bitmap b, int degrees) {
    179         return rotateAndMirror(b, degrees, false);
    180     }
    181 
    182     // Rotates and/or mirrors the bitmap. If a new bitmap is created, the
    183     // original bitmap is recycled.
    184     public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) {
    185         if ((degrees != 0 || mirror) && b != null) {
    186             Matrix m = new Matrix();
    187             // Mirror first.
    188             // horizontal flip + rotation = -rotation + horizontal flip
    189             if (mirror) {
    190                 m.postScale(-1, 1);
    191                 degrees = (degrees + 360) % 360;
    192                 if (degrees == 0 || degrees == 180) {
    193                     m.postTranslate(b.getWidth(), 0);
    194                 } else if (degrees == 90 || degrees == 270) {
    195                     m.postTranslate(b.getHeight(), 0);
    196                 } else {
    197                     throw new IllegalArgumentException("Invalid degrees=" + degrees);
    198                 }
    199             }
    200             if (degrees != 0) {
    201                 // clockwise
    202                 m.postRotate(degrees,
    203                         (float) b.getWidth() / 2, (float) b.getHeight() / 2);
    204             }
    205 
    206             try {
    207                 Bitmap b2 = Bitmap.createBitmap(
    208                         b, 0, 0, b.getWidth(), b.getHeight(), m, true);
    209                 if (b != b2) {
    210                     b.recycle();
    211                     b = b2;
    212                 }
    213             } catch (OutOfMemoryError ex) {
    214                 // We have no memory to rotate. Return the original bitmap.
    215             }
    216         }
    217         return b;
    218     }
    219 
    220     /*
    221      * Compute the sample size as a function of minSideLength
    222      * and maxNumOfPixels.
    223      * minSideLength is used to specify that minimal width or height of a
    224      * bitmap.
    225      * maxNumOfPixels is used to specify the maximal size in pixels that is
    226      * tolerable in terms of memory usage.
    227      *
    228      * The function returns a sample size based on the constraints.
    229      * Both size and minSideLength can be passed in as -1
    230      * which indicates no care of the corresponding constraint.
    231      * The functions prefers returning a sample size that
    232      * generates a smaller bitmap, unless minSideLength = -1.
    233      *
    234      * Also, the function rounds up the sample size to a power of 2 or multiple
    235      * of 8 because BitmapFactory only honors sample size this way.
    236      * For example, BitmapFactory downsamples an image by 2 even though the
    237      * request is 3. So we round up the sample size to avoid OOM.
    238      */
    239     public static int computeSampleSize(BitmapFactory.Options options,
    240             int minSideLength, int maxNumOfPixels) {
    241         int initialSize = computeInitialSampleSize(options, minSideLength,
    242                 maxNumOfPixels);
    243 
    244         int roundedSize;
    245         if (initialSize <= 8) {
    246             roundedSize = 1;
    247             while (roundedSize < initialSize) {
    248                 roundedSize <<= 1;
    249             }
    250         } else {
    251             roundedSize = (initialSize + 7) / 8 * 8;
    252         }
    253 
    254         return roundedSize;
    255     }
    256 
    257     private static int computeInitialSampleSize(BitmapFactory.Options options,
    258             int minSideLength, int maxNumOfPixels) {
    259         double w = options.outWidth;
    260         double h = options.outHeight;
    261 
    262         int lowerBound = (maxNumOfPixels < 0) ? 1 :
    263                 (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
    264         int upperBound = (minSideLength < 0) ? 128 :
    265                 (int) Math.min(Math.floor(w / minSideLength),
    266                 Math.floor(h / minSideLength));
    267 
    268         if (upperBound < lowerBound) {
    269             // return the larger one when there is no overlapping zone.
    270             return lowerBound;
    271         }
    272 
    273         if (maxNumOfPixels < 0 && minSideLength < 0) {
    274             return 1;
    275         } else if (minSideLength < 0) {
    276             return lowerBound;
    277         } else {
    278             return upperBound;
    279         }
    280     }
    281 
    282     public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) {
    283         try {
    284             BitmapFactory.Options options = new BitmapFactory.Options();
    285             options.inJustDecodeBounds = true;
    286             BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
    287                     options);
    288             if (options.mCancel || options.outWidth == -1
    289                     || options.outHeight == -1) {
    290                 return null;
    291             }
    292             options.inSampleSize = computeSampleSize(
    293                     options, -1, maxNumOfPixels);
    294             options.inJustDecodeBounds = false;
    295 
    296             options.inDither = false;
    297             options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    298             return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
    299                     options);
    300         } catch (OutOfMemoryError ex) {
    301             Log.e(TAG, "Got oom exception ", ex);
    302             return null;
    303         }
    304     }
    305 
    306     public static void closeSilently(Closeable c) {
    307         if (c == null) return;
    308         try {
    309             c.close();
    310         } catch (Throwable t) {
    311             // do nothing
    312         }
    313     }
    314 
    315     public static void Assert(boolean cond) {
    316         if (!cond) {
    317             throw new AssertionError();
    318         }
    319     }
    320 
    321     private static void throwIfCameraDisabled(Activity activity) throws CameraDisabledException {
    322         // Check if device policy has disabled the camera.
    323         DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService(
    324                 Context.DEVICE_POLICY_SERVICE);
    325         if (dpm.getCameraDisabled(null)) {
    326             throw new CameraDisabledException();
    327         }
    328     }
    329 
    330     public static CameraManager.CameraProxy openCamera(
    331             Activity activity, final int cameraId,
    332             Handler handler, final CameraManager.CameraOpenErrorCallback cb) {
    333         try {
    334             throwIfCameraDisabled(activity);
    335             return CameraHolder.instance().open(handler, cameraId, cb);
    336         } catch (CameraDisabledException ex) {
    337             handler.post(new Runnable() {
    338                 @Override
    339                 public void run() {
    340                     cb.onCameraDisabled(cameraId);
    341                 }
    342             });
    343         }
    344         return null;
    345     }
    346 
    347     public static void showErrorAndFinish(final Activity activity, int msgId) {
    348         DialogInterface.OnClickListener buttonListener =
    349                 new DialogInterface.OnClickListener() {
    350             @Override
    351             public void onClick(DialogInterface dialog, int which) {
    352                 activity.finish();
    353             }
    354         };
    355         TypedValue out = new TypedValue();
    356         activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true);
    357         new AlertDialog.Builder(activity)
    358                 .setCancelable(false)
    359                 .setTitle(R.string.camera_error_title)
    360                 .setMessage(msgId)
    361                 .setNeutralButton(R.string.dialog_ok, buttonListener)
    362                 .setIcon(out.resourceId)
    363                 .show();
    364     }
    365 
    366     public static <T> T checkNotNull(T object) {
    367         if (object == null) throw new NullPointerException();
    368         return object;
    369     }
    370 
    371     public static boolean equals(Object a, Object b) {
    372         return (a == b) || (a == null ? false : a.equals(b));
    373     }
    374 
    375     public static int nextPowerOf2(int n) {
    376         n -= 1;
    377         n |= n >>> 16;
    378         n |= n >>> 8;
    379         n |= n >>> 4;
    380         n |= n >>> 2;
    381         n |= n >>> 1;
    382         return n + 1;
    383     }
    384 
    385     public static float distance(float x, float y, float sx, float sy) {
    386         float dx = x - sx;
    387         float dy = y - sy;
    388         return (float) Math.sqrt(dx * dx + dy * dy);
    389     }
    390 
    391     public static int clamp(int x, int min, int max) {
    392         if (x > max) return max;
    393         if (x < min) return min;
    394         return x;
    395     }
    396 
    397     public static float clamp(float x, float min, float max) {
    398         if (x > max) return max;
    399         if (x < min) return min;
    400         return x;
    401     }
    402 
    403     public static int getDisplayRotation(Activity activity) {
    404         int rotation = activity.getWindowManager().getDefaultDisplay()
    405                 .getRotation();
    406         switch (rotation) {
    407             case Surface.ROTATION_0: return 0;
    408             case Surface.ROTATION_90: return 90;
    409             case Surface.ROTATION_180: return 180;
    410             case Surface.ROTATION_270: return 270;
    411         }
    412         return 0;
    413     }
    414 
    415     /**
    416      * Calculate the default orientation of the device based on the width and
    417      * height of the display when rotation = 0 (i.e. natural width and height)
    418      * @param activity the activity context
    419      * @return whether the default orientation of the device is portrait
    420      */
    421     public static boolean isDefaultToPortrait(Activity activity) {
    422         Display currentDisplay = activity.getWindowManager().getDefaultDisplay();
    423         Point displaySize = new Point();
    424         currentDisplay.getSize(displaySize);
    425         int orientation = currentDisplay.getRotation();
    426         int naturalWidth, naturalHeight;
    427         if (orientation == Surface.ROTATION_0 || orientation == Surface.ROTATION_180) {
    428             naturalWidth = displaySize.x;
    429             naturalHeight = displaySize.y;
    430         } else {
    431             naturalWidth = displaySize.y;
    432             naturalHeight = displaySize.x;
    433         }
    434         return naturalWidth < naturalHeight;
    435     }
    436 
    437     public static int getDisplayOrientation(int degrees, int cameraId) {
    438         // See android.hardware.Camera.setDisplayOrientation for
    439         // documentation.
    440         Camera.CameraInfo info = new Camera.CameraInfo();
    441         Camera.getCameraInfo(cameraId, info);
    442         int result;
    443         if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
    444             result = (info.orientation + degrees) % 360;
    445             result = (360 - result) % 360;  // compensate the mirror
    446         } else {  // back-facing
    447             result = (info.orientation - degrees + 360) % 360;
    448         }
    449         return result;
    450     }
    451 
    452     public static int getCameraOrientation(int cameraId) {
    453         Camera.CameraInfo info = new Camera.CameraInfo();
    454         Camera.getCameraInfo(cameraId, info);
    455         return info.orientation;
    456     }
    457 
    458     public static int roundOrientation(int orientation, int orientationHistory) {
    459         boolean changeOrientation = false;
    460         if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
    461             changeOrientation = true;
    462         } else {
    463             int dist = Math.abs(orientation - orientationHistory);
    464             dist = Math.min( dist, 360 - dist );
    465             changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS );
    466         }
    467         if (changeOrientation) {
    468             return ((orientation + 45) / 90 * 90) % 360;
    469         }
    470         return orientationHistory;
    471     }
    472 
    473     private static Point getDefaultDisplaySize(Activity activity, Point size) {
    474         activity.getWindowManager().getDefaultDisplay().getSize(size);
    475         return size;
    476     }
    477 
    478     public static Size getOptimalPreviewSize(Activity currentActivity,
    479             List<Size> sizes, double targetRatio) {
    480 
    481         Point[] points = new Point[sizes.size()];
    482 
    483         int index = 0;
    484         for (Size s : sizes) {
    485             points[index++] = new Point(s.width, s.height);
    486         }
    487 
    488         int optimalPickIndex = getOptimalPreviewSize(currentActivity, points, targetRatio);
    489         return (optimalPickIndex == -1) ? null : sizes.get(optimalPickIndex);
    490     }
    491 
    492     public static int getOptimalPreviewSize(Activity currentActivity,
    493             Point[] sizes, double targetRatio) {
    494         // Use a very small tolerance because we want an exact match.
    495         final double ASPECT_TOLERANCE = 0.01;
    496         if (sizes == null) return -1;
    497 
    498         int optimalSizeIndex = -1;
    499         double minDiff = Double.MAX_VALUE;
    500 
    501         // Because of bugs of overlay and layout, we sometimes will try to
    502         // layout the viewfinder in the portrait orientation and thus get the
    503         // wrong size of preview surface. When we change the preview size, the
    504         // new overlay will be created before the old one closed, which causes
    505         // an exception. For now, just get the screen size.
    506         Point point = getDefaultDisplaySize(currentActivity, new Point());
    507         int targetHeight = Math.min(point.x, point.y);
    508         // Try to find an size match aspect ratio and size
    509         for (int i = 0; i < sizes.length; i++) {
    510             Point size = sizes[i];
    511             double ratio = (double) size.x / size.y;
    512             if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
    513             if (Math.abs(size.y - targetHeight) < minDiff) {
    514                 optimalSizeIndex = i;
    515                 minDiff = Math.abs(size.y - targetHeight);
    516             }
    517         }
    518         // Cannot find the one match the aspect ratio. This should not happen.
    519         // Ignore the requirement.
    520         if (optimalSizeIndex == -1) {
    521             Log.w(TAG, "No preview size match the aspect ratio");
    522             minDiff = Double.MAX_VALUE;
    523             for (int i = 0; i < sizes.length; i++) {
    524                 Point size = sizes[i];
    525                 if (Math.abs(size.y - targetHeight) < minDiff) {
    526                     optimalSizeIndex = i;
    527                     minDiff = Math.abs(size.y - targetHeight);
    528                 }
    529             }
    530         }
    531         return optimalSizeIndex;
    532     }
    533 
    534     // Returns the largest picture size which matches the given aspect ratio.
    535     public static Size getOptimalVideoSnapshotPictureSize(
    536             List<Size> sizes, double targetRatio) {
    537         // Use a very small tolerance because we want an exact match.
    538         final double ASPECT_TOLERANCE = 0.001;
    539         if (sizes == null) return null;
    540 
    541         Size optimalSize = null;
    542 
    543         // Try to find a size matches aspect ratio and has the largest width
    544         for (Size size : sizes) {
    545             double ratio = (double) size.width / size.height;
    546             if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
    547             if (optimalSize == null || size.width > optimalSize.width) {
    548                 optimalSize = size;
    549             }
    550         }
    551 
    552         // Cannot find one that matches the aspect ratio. This should not happen.
    553         // Ignore the requirement.
    554         if (optimalSize == null) {
    555             Log.w(TAG, "No picture size match the aspect ratio");
    556             for (Size size : sizes) {
    557                 if (optimalSize == null || size.width > optimalSize.width) {
    558                     optimalSize = size;
    559                 }
    560             }
    561         }
    562         return optimalSize;
    563     }
    564 
    565     public static void dumpParameters(Parameters parameters) {
    566         String flattened = parameters.flatten();
    567         StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
    568         Log.d(TAG, "Dump all camera parameters:");
    569         while (tokenizer.hasMoreElements()) {
    570             Log.d(TAG, tokenizer.nextToken());
    571         }
    572     }
    573 
    574     /**
    575      * Returns whether the device is voice-capable (meaning, it can do MMS).
    576      */
    577     public static boolean isMmsCapable(Context context) {
    578         TelephonyManager telephonyManager = (TelephonyManager)
    579                 context.getSystemService(Context.TELEPHONY_SERVICE);
    580         if (telephonyManager == null) {
    581             return false;
    582         }
    583 
    584         try {
    585             Class<?> partypes[] = new Class[0];
    586             Method sIsVoiceCapable = TelephonyManager.class.getMethod(
    587                     "isVoiceCapable", partypes);
    588 
    589             Object arglist[] = new Object[0];
    590             Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist);
    591             return (Boolean) retobj;
    592         } catch (java.lang.reflect.InvocationTargetException ite) {
    593             // Failure, must be another device.
    594             // Assume that it is voice capable.
    595         } catch (IllegalAccessException iae) {
    596             // Failure, must be an other device.
    597             // Assume that it is voice capable.
    598         } catch (NoSuchMethodException nsme) {
    599         }
    600         return true;
    601     }
    602 
    603     // This is for test only. Allow the camera to launch the specific camera.
    604     public static int getCameraFacingIntentExtras(Activity currentActivity) {
    605         int cameraId = -1;
    606 
    607         int intentCameraId =
    608                 currentActivity.getIntent().getIntExtra(CameraUtil.EXTRAS_CAMERA_FACING, -1);
    609 
    610         if (isFrontCameraIntent(intentCameraId)) {
    611             // Check if the front camera exist
    612             int frontCameraId = CameraHolder.instance().getFrontCameraId();
    613             if (frontCameraId != -1) {
    614                 cameraId = frontCameraId;
    615             }
    616         } else if (isBackCameraIntent(intentCameraId)) {
    617             // Check if the back camera exist
    618             int backCameraId = CameraHolder.instance().getBackCameraId();
    619             if (backCameraId != -1) {
    620                 cameraId = backCameraId;
    621             }
    622         }
    623         return cameraId;
    624     }
    625 
    626     private static boolean isFrontCameraIntent(int intentCameraId) {
    627         return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
    628     }
    629 
    630     private static boolean isBackCameraIntent(int intentCameraId) {
    631         return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
    632     }
    633 
    634     private static int sLocation[] = new int[2];
    635 
    636     // This method is not thread-safe.
    637     public static boolean pointInView(float x, float y, View v) {
    638         v.getLocationInWindow(sLocation);
    639         return x >= sLocation[0] && x < (sLocation[0] + v.getWidth())
    640                 && y >= sLocation[1] && y < (sLocation[1] + v.getHeight());
    641     }
    642 
    643     public static int[] getRelativeLocation(View reference, View view) {
    644         reference.getLocationInWindow(sLocation);
    645         int referenceX = sLocation[0];
    646         int referenceY = sLocation[1];
    647         view.getLocationInWindow(sLocation);
    648         sLocation[0] -= referenceX;
    649         sLocation[1] -= referenceY;
    650         return sLocation;
    651     }
    652 
    653     public static boolean isUriValid(Uri uri, ContentResolver resolver) {
    654         if (uri == null) return false;
    655 
    656         try {
    657             ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
    658             if (pfd == null) {
    659                 Log.e(TAG, "Fail to open URI. URI=" + uri);
    660                 return false;
    661             }
    662             pfd.close();
    663         } catch (IOException ex) {
    664             return false;
    665         }
    666         return true;
    667     }
    668 
    669     public static void dumpRect(RectF rect, String msg) {
    670         Log.v(TAG, msg + "=(" + rect.left + "," + rect.top
    671                 + "," + rect.right + "," + rect.bottom + ")");
    672     }
    673 
    674     public static void rectFToRect(RectF rectF, Rect rect) {
    675         rect.left = Math.round(rectF.left);
    676         rect.top = Math.round(rectF.top);
    677         rect.right = Math.round(rectF.right);
    678         rect.bottom = Math.round(rectF.bottom);
    679     }
    680 
    681     public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
    682             int viewWidth, int viewHeight) {
    683         // Need mirror for front camera.
    684         matrix.setScale(mirror ? -1 : 1, 1);
    685         // This is the value for android.hardware.Camera.setDisplayOrientation.
    686         matrix.postRotate(displayOrientation);
    687         // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
    688         // UI coordinates range from (0, 0) to (width, height).
    689         matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
    690         matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
    691     }
    692 
    693     public static String createJpegName(long dateTaken) {
    694         synchronized (sImageFileNamer) {
    695             return sImageFileNamer.generateName(dateTaken);
    696         }
    697     }
    698 
    699     public static void broadcastNewPicture(Context context, Uri uri) {
    700         context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri));
    701         // Keep compatibility
    702         context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri));
    703     }
    704 
    705     public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) {
    706         if (view.getVisibility() == View.VISIBLE) return;
    707 
    708         view.setVisibility(View.VISIBLE);
    709         Animation animation = new AlphaAnimation(startAlpha, endAlpha);
    710         animation.setDuration(duration);
    711         view.startAnimation(animation);
    712     }
    713 
    714     public static void fadeIn(View view) {
    715         fadeIn(view, 0F, 1F, 400);
    716 
    717         // We disabled the button in fadeOut(), so enable it here.
    718         view.setEnabled(true);
    719     }
    720 
    721     public static void fadeOut(View view) {
    722         if (view.getVisibility() != View.VISIBLE) return;
    723 
    724         // Since the button is still clickable before fade-out animation
    725         // ends, we disable the button first to block click.
    726         view.setEnabled(false);
    727         Animation animation = new AlphaAnimation(1F, 0F);
    728         animation.setDuration(400);
    729         view.startAnimation(animation);
    730         view.setVisibility(View.GONE);
    731     }
    732 
    733     public static int getJpegRotation(int cameraId, int orientation) {
    734         // See android.hardware.Camera.Parameters.setRotation for
    735         // documentation.
    736         int rotation = 0;
    737         if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
    738             CameraInfo info = CameraHolder.instance().getCameraInfo()[cameraId];
    739             if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
    740                 rotation = (info.orientation - orientation + 360) % 360;
    741             } else {  // back-facing camera
    742                 rotation = (info.orientation + orientation) % 360;
    743             }
    744         }
    745         return rotation;
    746     }
    747 
    748     /**
    749      * Down-samples a jpeg byte array.
    750      * @param data a byte array of jpeg data
    751      * @param downSampleFactor down-sample factor
    752      * @return decoded and down-sampled bitmap
    753      */
    754     public static Bitmap downSample(final byte[] data, int downSampleFactor) {
    755         final BitmapFactory.Options opts = new BitmapFactory.Options();
    756         // Downsample the image
    757         opts.inSampleSize = downSampleFactor;
    758         return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
    759     }
    760 
    761     public static void setGpsParameters(Parameters parameters, Location loc) {
    762         // Clear previous GPS location from the parameters.
    763         parameters.removeGpsData();
    764 
    765         // We always encode GpsTimeStamp
    766         parameters.setGpsTimestamp(System.currentTimeMillis() / 1000);
    767 
    768         // Set GPS location.
    769         if (loc != null) {
    770             double lat = loc.getLatitude();
    771             double lon = loc.getLongitude();
    772             boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
    773 
    774             if (hasLatLon) {
    775                 Log.d(TAG, "Set gps location");
    776                 parameters.setGpsLatitude(lat);
    777                 parameters.setGpsLongitude(lon);
    778                 parameters.setGpsProcessingMethod(loc.getProvider().toUpperCase());
    779                 if (loc.hasAltitude()) {
    780                     parameters.setGpsAltitude(loc.getAltitude());
    781                 } else {
    782                     // for NETWORK_PROVIDER location provider, we may have
    783                     // no altitude information, but the driver needs it, so
    784                     // we fake one.
    785                     parameters.setGpsAltitude(0);
    786                 }
    787                 if (loc.getTime() != 0) {
    788                     // Location.getTime() is UTC in milliseconds.
    789                     // gps-timestamp is UTC in seconds.
    790                     long utcTimeSeconds = loc.getTime() / 1000;
    791                     parameters.setGpsTimestamp(utcTimeSeconds);
    792                 }
    793             } else {
    794                 loc = null;
    795             }
    796         }
    797     }
    798 
    799     /**
    800      * For still image capture, we need to get the right fps range such that the
    801      * camera can slow down the framerate to allow for less-noisy/dark
    802      * viewfinder output in dark conditions.
    803      *
    804      * @param params Camera's parameters.
    805      * @return null if no appropiate fps range can't be found. Otherwise, return
    806      *         the right range.
    807      */
    808     public static int[] getPhotoPreviewFpsRange(Parameters params) {
    809         List<int[]> frameRates = params.getSupportedPreviewFpsRange();
    810         if (frameRates.size() == 0) {
    811             Log.e(TAG, "No suppoted frame rates returned!");
    812             return null;
    813         }
    814 
    815         // Find the lowest min rate in supported ranges who can cover 30fps.
    816         int lowestMinRate = MAX_PREVIEW_FPS_TIMES_1000;
    817         for (int[] rate : frameRates) {
    818             int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX];
    819             int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX];
    820             if (maxFps >= PREFERRED_PREVIEW_FPS_TIMES_1000 &&
    821                     minFps <= PREFERRED_PREVIEW_FPS_TIMES_1000 &&
    822                     minFps < lowestMinRate) {
    823                 lowestMinRate = minFps;
    824             }
    825         }
    826 
    827         // Find all the modes with the lowest min rate found above, the pick the
    828         // one with highest max rate.
    829         int resultIndex = -1;
    830         int highestMaxRate = 0;
    831         for (int i = 0; i < frameRates.size(); i++) {
    832             int[] rate = frameRates.get(i);
    833             int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX];
    834             int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX];
    835             if (minFps == lowestMinRate && highestMaxRate < maxFps) {
    836                 highestMaxRate = maxFps;
    837                 resultIndex = i;
    838             }
    839         }
    840 
    841         if (resultIndex >= 0) {
    842             return frameRates.get(resultIndex);
    843         }
    844         Log.e(TAG, "Can't find an appropiate frame rate range!");
    845         return null;
    846     }
    847 
    848     public static int[] getMaxPreviewFpsRange(Parameters params) {
    849         List<int[]> frameRates = params.getSupportedPreviewFpsRange();
    850         if (frameRates != null && frameRates.size() > 0) {
    851             // The list is sorted. Return the last element.
    852             return frameRates.get(frameRates.size() - 1);
    853         }
    854         return new int[0];
    855     }
    856 
    857     private static class ImageFileNamer {
    858         private final SimpleDateFormat mFormat;
    859 
    860         // The date (in milliseconds) used to generate the last name.
    861         private long mLastDate;
    862 
    863         // Number of names generated for the same second.
    864         private int mSameSecondCount;
    865 
    866         public ImageFileNamer(String format) {
    867             mFormat = new SimpleDateFormat(format);
    868         }
    869 
    870         public String generateName(long dateTaken) {
    871             Date date = new Date(dateTaken);
    872             String result = mFormat.format(date);
    873 
    874             // If the last name was generated for the same second,
    875             // we append _1, _2, etc to the name.
    876             if (dateTaken / 1000 == mLastDate / 1000) {
    877                 mSameSecondCount++;
    878                 result += "_" + mSameSecondCount;
    879             } else {
    880                 mLastDate = dateTaken;
    881                 mSameSecondCount = 0;
    882             }
    883 
    884             return result;
    885         }
    886     }
    887 
    888     public static void playVideo(Activity activity, Uri uri, String title) {
    889         try {
    890             boolean isSecureCamera = ((CameraActivity)activity).isSecureCamera();
    891             if (!isSecureCamera) {
    892                 Intent intent = IntentHelper.getVideoPlayerIntent(activity, uri)
    893                         .putExtra(Intent.EXTRA_TITLE, title)
    894                         .putExtra(KEY_TREAT_UP_AS_BACK, true);
    895                 activity.startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
    896             } else {
    897                 // In order not to send out any intent to be intercepted and
    898                 // show the lock screen immediately, we just let the secure
    899                 // camera activity finish.
    900                 activity.finish();
    901             }
    902         } catch (ActivityNotFoundException e) {
    903             Toast.makeText(activity, activity.getString(R.string.video_err),
    904                     Toast.LENGTH_SHORT).show();
    905         }
    906     }
    907 
    908     /**
    909      * Starts GMM with the given location shown. If this fails, and GMM could
    910      * not be found, we use a geo intent as a fallback.
    911      *
    912      * @param activity the activity to use for launching the Maps intent.
    913      * @param latLong a 2-element array containing {latitude/longitude}.
    914      */
    915     public static void showOnMap(Activity activity, double[] latLong) {
    916         try {
    917             // We don't use "geo:latitude,longitude" because it only centers
    918             // the MapView to the specified location, but we need a marker
    919             // for further operations (routing to/from).
    920             // The q=(lat, lng) syntax is suggested by geo-team.
    921             String uri = String.format(Locale.ENGLISH, "http://maps.google.com/maps?f=q&q=(%f,%f)",
    922                     latLong[0], latLong[1]);
    923             ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME,
    924                     MAPS_CLASS_NAME);
    925             Intent mapsIntent = new Intent(Intent.ACTION_VIEW,
    926                     Uri.parse(uri)).setComponent(compName);
    927             activity.startActivityForResult(mapsIntent,
    928                     CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
    929         } catch (ActivityNotFoundException e) {
    930             // Use the "geo intent" if no GMM is installed
    931             Log.e(TAG, "GMM activity not found!", e);
    932             String url = String.format(Locale.ENGLISH, "geo:%f,%f", latLong[0], latLong[1]);
    933             Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
    934             activity.startActivity(mapsIntent);
    935         }
    936     }
    937 
    938     /**
    939      * Dumps the stack trace.
    940      *
    941      * @param level How many levels of the stack are dumped. 0 means all.
    942      * @return A {@link java.lang.String} of all the output with newline
    943      * between each.
    944      */
    945     public static String dumpStackTrace(int level) {
    946         StackTraceElement[] elems = Thread.currentThread().getStackTrace();
    947         // Ignore the first 3 elements.
    948         level = (level == 0 ? elems.length : Math.min(level + 3, elems.length));
    949         String ret = new String();
    950         for (int i = 3; i < level; i++) {
    951             ret = ret + "\t" + elems[i].toString() + '\n';
    952         }
    953         return ret;
    954     }
    955 
    956     /**
    957      * Launches apps supporting action {@link Intent.ACTION_MAIN} of category
    958      * {@link Intent.CATEGORY_APP_GALLERY}. Note that
    959      * {@link Intent.CATEGORY_APP_GALLERY} is only available on API level 15+.
    960      *
    961      * @param ctx The {@link android.content.Context} to launch the app.
    962      * @return {@code true} on success.
    963      */
    964     public static boolean launchGallery(Context ctx) {
    965         if (ApiHelper.HAS_APP_GALLERY) {
    966             ctx.startActivity(IntentHelper.getGalleryIntent(ctx));
    967             return true;
    968         }
    969         return false;
    970     }
    971 }
    972