Home | History | Annotate | Download | only in camera
      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;
     18 
     19 import android.annotation.TargetApi;
     20 import android.app.Activity;
     21 import android.app.AlertDialog;
     22 import android.app.admin.DevicePolicyManager;
     23 import android.content.ActivityNotFoundException;
     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.Build;
     41 import android.os.ParcelFileDescriptor;
     42 import android.provider.Settings;
     43 import android.telephony.TelephonyManager;
     44 import android.util.DisplayMetrics;
     45 import android.util.FloatMath;
     46 import android.util.Log;
     47 import android.util.TypedValue;
     48 import android.view.Display;
     49 import android.view.OrientationEventListener;
     50 import android.view.Surface;
     51 import android.view.View;
     52 import android.view.WindowManager;
     53 import android.view.animation.AlphaAnimation;
     54 import android.view.animation.Animation;
     55 
     56 import com.android.gallery3d.R;
     57 import com.android.gallery3d.common.ApiHelper;
     58 
     59 import java.io.Closeable;
     60 import java.io.IOException;
     61 import java.lang.reflect.Method;
     62 import java.text.SimpleDateFormat;
     63 import java.util.Date;
     64 import java.util.List;
     65 import java.util.StringTokenizer;
     66 
     67 /**
     68  * Collection of utility functions used in this package.
     69  */
     70 public class Util {
     71     private static final String TAG = "Util";
     72 
     73     // Orientation hysteresis amount used in rounding, in degrees
     74     public static final int ORIENTATION_HYSTERESIS = 5;
     75 
     76     public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW";
     77     // See android.hardware.Camera.ACTION_NEW_PICTURE.
     78     public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
     79     // See android.hardware.Camera.ACTION_NEW_VIDEO.
     80     public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
     81 
     82     // Fields from android.hardware.Camera.Parameters
     83     public static final String FOCUS_MODE_CONTINUOUS_PICTURE = "continuous-picture";
     84     public static final String RECORDING_HINT = "recording-hint";
     85     private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
     86     private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported";
     87     private static final String VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported";
     88     public static final String SCENE_MODE_HDR = "hdr";
     89     public static final String TRUE = "true";
     90     public static final String FALSE = "false";
     91 
     92     public static boolean isSupported(String value, List<String> supported) {
     93         return supported == null ? false : supported.indexOf(value) >= 0;
     94     }
     95 
     96     public static boolean isAutoExposureLockSupported(Parameters params) {
     97         return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
     98     }
     99 
    100     public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
    101         return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
    102     }
    103 
    104     public static boolean isVideoSnapshotSupported(Parameters params) {
    105         return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED));
    106     }
    107 
    108     public static boolean isCameraHdrSupported(Parameters params) {
    109         List<String> supported = params.getSupportedSceneModes();
    110         return (supported != null) && supported.contains(SCENE_MODE_HDR);
    111     }
    112 
    113     @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
    114     public static boolean isMeteringAreaSupported(Parameters params) {
    115         if (ApiHelper.HAS_CAMERA_METERING_AREA) {
    116             return params.getMaxNumMeteringAreas() > 0;
    117         }
    118         return false;
    119     }
    120 
    121     @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
    122     public static boolean isFocusAreaSupported(Parameters params) {
    123         if (ApiHelper.HAS_CAMERA_FOCUS_AREA) {
    124             return (params.getMaxNumFocusAreas() > 0
    125                     && isSupported(Parameters.FOCUS_MODE_AUTO,
    126                             params.getSupportedFocusModes()));
    127         }
    128         return false;
    129     }
    130 
    131     // Private intent extras. Test only.
    132     private static final String EXTRAS_CAMERA_FACING =
    133             "android.intent.extras.CAMERA_FACING";
    134 
    135     private static float sPixelDensity = 1;
    136     private static ImageFileNamer sImageFileNamer;
    137 
    138     private Util() {
    139     }
    140 
    141     public static void initialize(Context context) {
    142         DisplayMetrics metrics = new DisplayMetrics();
    143         WindowManager wm = (WindowManager)
    144                 context.getSystemService(Context.WINDOW_SERVICE);
    145         wm.getDefaultDisplay().getMetrics(metrics);
    146         sPixelDensity = metrics.density;
    147         sImageFileNamer = new ImageFileNamer(
    148                 context.getString(R.string.image_file_name_format));
    149     }
    150 
    151     public static int dpToPixel(int dp) {
    152         return Math.round(sPixelDensity * dp);
    153     }
    154 
    155     // Rotates the bitmap by the specified degree.
    156     // If a new bitmap is created, the original bitmap is recycled.
    157     public static Bitmap rotate(Bitmap b, int degrees) {
    158         return rotateAndMirror(b, degrees, false);
    159     }
    160 
    161     // Rotates and/or mirrors the bitmap. If a new bitmap is created, the
    162     // original bitmap is recycled.
    163     public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) {
    164         if ((degrees != 0 || mirror) && b != null) {
    165             Matrix m = new Matrix();
    166             // Mirror first.
    167             // horizontal flip + rotation = -rotation + horizontal flip
    168             if (mirror) {
    169                 m.postScale(-1, 1);
    170                 degrees = (degrees + 360) % 360;
    171                 if (degrees == 0 || degrees == 180) {
    172                     m.postTranslate(b.getWidth(), 0);
    173                 } else if (degrees == 90 || degrees == 270) {
    174                     m.postTranslate(b.getHeight(), 0);
    175                 } else {
    176                     throw new IllegalArgumentException("Invalid degrees=" + degrees);
    177                 }
    178             }
    179             if (degrees != 0) {
    180                 // clockwise
    181                 m.postRotate(degrees,
    182                         (float) b.getWidth() / 2, (float) b.getHeight() / 2);
    183             }
    184 
    185             try {
    186                 Bitmap b2 = Bitmap.createBitmap(
    187                         b, 0, 0, b.getWidth(), b.getHeight(), m, true);
    188                 if (b != b2) {
    189                     b.recycle();
    190                     b = b2;
    191                 }
    192             } catch (OutOfMemoryError ex) {
    193                 // We have no memory to rotate. Return the original bitmap.
    194             }
    195         }
    196         return b;
    197     }
    198 
    199     /*
    200      * Compute the sample size as a function of minSideLength
    201      * and maxNumOfPixels.
    202      * minSideLength is used to specify that minimal width or height of a
    203      * bitmap.
    204      * maxNumOfPixels is used to specify the maximal size in pixels that is
    205      * tolerable in terms of memory usage.
    206      *
    207      * The function returns a sample size based on the constraints.
    208      * Both size and minSideLength can be passed in as -1
    209      * which indicates no care of the corresponding constraint.
    210      * The functions prefers returning a sample size that
    211      * generates a smaller bitmap, unless minSideLength = -1.
    212      *
    213      * Also, the function rounds up the sample size to a power of 2 or multiple
    214      * of 8 because BitmapFactory only honors sample size this way.
    215      * For example, BitmapFactory downsamples an image by 2 even though the
    216      * request is 3. So we round up the sample size to avoid OOM.
    217      */
    218     public static int computeSampleSize(BitmapFactory.Options options,
    219             int minSideLength, int maxNumOfPixels) {
    220         int initialSize = computeInitialSampleSize(options, minSideLength,
    221                 maxNumOfPixels);
    222 
    223         int roundedSize;
    224         if (initialSize <= 8) {
    225             roundedSize = 1;
    226             while (roundedSize < initialSize) {
    227                 roundedSize <<= 1;
    228             }
    229         } else {
    230             roundedSize = (initialSize + 7) / 8 * 8;
    231         }
    232 
    233         return roundedSize;
    234     }
    235 
    236     private static int computeInitialSampleSize(BitmapFactory.Options options,
    237             int minSideLength, int maxNumOfPixels) {
    238         double w = options.outWidth;
    239         double h = options.outHeight;
    240 
    241         int lowerBound = (maxNumOfPixels < 0) ? 1 :
    242                 (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
    243         int upperBound = (minSideLength < 0) ? 128 :
    244                 (int) Math.min(Math.floor(w / minSideLength),
    245                 Math.floor(h / minSideLength));
    246 
    247         if (upperBound < lowerBound) {
    248             // return the larger one when there is no overlapping zone.
    249             return lowerBound;
    250         }
    251 
    252         if (maxNumOfPixels < 0 && minSideLength < 0) {
    253             return 1;
    254         } else if (minSideLength < 0) {
    255             return lowerBound;
    256         } else {
    257             return upperBound;
    258         }
    259     }
    260 
    261     public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) {
    262         try {
    263             BitmapFactory.Options options = new BitmapFactory.Options();
    264             options.inJustDecodeBounds = true;
    265             BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
    266                     options);
    267             if (options.mCancel || options.outWidth == -1
    268                     || options.outHeight == -1) {
    269                 return null;
    270             }
    271             options.inSampleSize = computeSampleSize(
    272                     options, -1, maxNumOfPixels);
    273             options.inJustDecodeBounds = false;
    274 
    275             options.inDither = false;
    276             options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    277             return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
    278                     options);
    279         } catch (OutOfMemoryError ex) {
    280             Log.e(TAG, "Got oom exception ", ex);
    281             return null;
    282         }
    283     }
    284 
    285     public static void closeSilently(Closeable c) {
    286         if (c == null) return;
    287         try {
    288             c.close();
    289         } catch (Throwable t) {
    290             // do nothing
    291         }
    292     }
    293 
    294     public static void Assert(boolean cond) {
    295         if (!cond) {
    296             throw new AssertionError();
    297         }
    298     }
    299 
    300     @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
    301     private static void throwIfCameraDisabled(Activity activity) throws CameraDisabledException {
    302         // Check if device policy has disabled the camera.
    303         if (ApiHelper.HAS_GET_CAMERA_DISABLED) {
    304             DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService(
    305                     Context.DEVICE_POLICY_SERVICE);
    306             if (dpm.getCameraDisabled(null)) {
    307                 throw new CameraDisabledException();
    308             }
    309         }
    310     }
    311 
    312     public static CameraManager.CameraProxy openCamera(Activity activity, int cameraId)
    313             throws CameraHardwareException, CameraDisabledException {
    314         throwIfCameraDisabled(activity);
    315 
    316         try {
    317             return CameraHolder.instance().open(cameraId);
    318         } catch (CameraHardwareException e) {
    319             // In eng build, we throw the exception so that test tool
    320             // can detect it and report it
    321             if ("eng".equals(Build.TYPE)) {
    322                 throw new RuntimeException("openCamera failed", e);
    323             } else {
    324                 throw e;
    325             }
    326         }
    327     }
    328 
    329     public static void showErrorAndFinish(final Activity activity, int msgId) {
    330         DialogInterface.OnClickListener buttonListener =
    331                 new DialogInterface.OnClickListener() {
    332             @Override
    333             public void onClick(DialogInterface dialog, int which) {
    334                 activity.finish();
    335             }
    336         };
    337         TypedValue out = new TypedValue();
    338         activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true);
    339         new AlertDialog.Builder(activity)
    340                 .setCancelable(false)
    341                 .setTitle(R.string.camera_error_title)
    342                 .setMessage(msgId)
    343                 .setNeutralButton(R.string.dialog_ok, buttonListener)
    344                 .setIcon(out.resourceId)
    345                 .show();
    346     }
    347 
    348     public static <T> T checkNotNull(T object) {
    349         if (object == null) throw new NullPointerException();
    350         return object;
    351     }
    352 
    353     public static boolean equals(Object a, Object b) {
    354         return (a == b) || (a == null ? false : a.equals(b));
    355     }
    356 
    357     public static int nextPowerOf2(int n) {
    358         n -= 1;
    359         n |= n >>> 16;
    360         n |= n >>> 8;
    361         n |= n >>> 4;
    362         n |= n >>> 2;
    363         n |= n >>> 1;
    364         return n + 1;
    365     }
    366 
    367     public static float distance(float x, float y, float sx, float sy) {
    368         float dx = x - sx;
    369         float dy = y - sy;
    370         return FloatMath.sqrt(dx * dx + dy * dy);
    371     }
    372 
    373     public static int clamp(int x, int min, int max) {
    374         if (x > max) return max;
    375         if (x < min) return min;
    376         return x;
    377     }
    378 
    379     public static boolean systemRotationLocked(Activity activity) {
    380         return Settings.System.getInt(activity.getContentResolver(),
    381                 Settings.System.ACCELEROMETER_ROTATION, 0) == 0;
    382     }
    383 
    384     public static int getDisplayRotation(Activity activity) {
    385         int rotation = activity.getWindowManager().getDefaultDisplay()
    386                 .getRotation();
    387         switch (rotation) {
    388             case Surface.ROTATION_0: return 0;
    389             case Surface.ROTATION_90: return 90;
    390             case Surface.ROTATION_180: return 180;
    391             case Surface.ROTATION_270: return 270;
    392         }
    393         return 0;
    394     }
    395 
    396     public static int getDisplayOrientation(int degrees, int cameraId) {
    397         // See android.hardware.Camera.setDisplayOrientation for
    398         // documentation.
    399         Camera.CameraInfo info = new Camera.CameraInfo();
    400         Camera.getCameraInfo(cameraId, info);
    401         int result;
    402         if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
    403             result = (info.orientation + degrees) % 360;
    404             result = (360 - result) % 360;  // compensate the mirror
    405         } else {  // back-facing
    406             result = (info.orientation - degrees + 360) % 360;
    407         }
    408         return result;
    409     }
    410 
    411     public static int getCameraOrientation(int cameraId) {
    412         Camera.CameraInfo info = new Camera.CameraInfo();
    413         Camera.getCameraInfo(cameraId, info);
    414         return info.orientation;
    415     }
    416 
    417     public static int roundOrientation(int orientation, int orientationHistory) {
    418         boolean changeOrientation = false;
    419         if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
    420             changeOrientation = true;
    421         } else {
    422             int dist = Math.abs(orientation - orientationHistory);
    423             dist = Math.min( dist, 360 - dist );
    424             changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS );
    425         }
    426         if (changeOrientation) {
    427             return ((orientation + 45) / 90 * 90) % 360;
    428         }
    429         return orientationHistory;
    430     }
    431 
    432     @SuppressWarnings("deprecation")
    433     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
    434     private static Point getDefaultDisplaySize(Activity activity, Point size) {
    435         Display d = activity.getWindowManager().getDefaultDisplay();
    436         if (Build.VERSION.SDK_INT >= ApiHelper.VERSION_CODES.HONEYCOMB_MR2) {
    437             d.getSize(size);
    438         } else {
    439             size.set(d.getWidth(), d.getHeight());
    440         }
    441         return size;
    442     }
    443 
    444     public static Size getOptimalPreviewSize(Activity currentActivity,
    445             List<Size> sizes, double targetRatio) {
    446         // Use a very small tolerance because we want an exact match.
    447         final double ASPECT_TOLERANCE = 0.001;
    448         if (sizes == null) return null;
    449 
    450         Size optimalSize = null;
    451         double minDiff = Double.MAX_VALUE;
    452 
    453         // Because of bugs of overlay and layout, we sometimes will try to
    454         // layout the viewfinder in the portrait orientation and thus get the
    455         // wrong size of preview surface. When we change the preview size, the
    456         // new overlay will be created before the old one closed, which causes
    457         // an exception. For now, just get the screen size.
    458         Point point = getDefaultDisplaySize(currentActivity, new Point());
    459         int targetHeight = Math.min(point.x, point.y);
    460         // Try to find an size match aspect ratio and size
    461         for (Size size : sizes) {
    462             double ratio = (double) size.width / size.height;
    463             if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
    464             if (Math.abs(size.height - targetHeight) < minDiff) {
    465                 optimalSize = size;
    466                 minDiff = Math.abs(size.height - targetHeight);
    467             }
    468         }
    469         // Cannot find the one match the aspect ratio. This should not happen.
    470         // Ignore the requirement.
    471         if (optimalSize == null) {
    472             Log.w(TAG, "No preview size match the aspect ratio");
    473             minDiff = Double.MAX_VALUE;
    474             for (Size size : sizes) {
    475                 if (Math.abs(size.height - targetHeight) < minDiff) {
    476                     optimalSize = size;
    477                     minDiff = Math.abs(size.height - targetHeight);
    478                 }
    479             }
    480         }
    481         return optimalSize;
    482     }
    483 
    484     // Returns the largest picture size which matches the given aspect ratio.
    485     public static Size getOptimalVideoSnapshotPictureSize(
    486             List<Size> sizes, double targetRatio) {
    487         // Use a very small tolerance because we want an exact match.
    488         final double ASPECT_TOLERANCE = 0.001;
    489         if (sizes == null) return null;
    490 
    491         Size optimalSize = null;
    492 
    493         // Try to find a size matches aspect ratio and has the largest width
    494         for (Size size : sizes) {
    495             double ratio = (double) size.width / size.height;
    496             if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
    497             if (optimalSize == null || size.width > optimalSize.width) {
    498                 optimalSize = size;
    499             }
    500         }
    501 
    502         // Cannot find one that matches the aspect ratio. This should not happen.
    503         // Ignore the requirement.
    504         if (optimalSize == null) {
    505             Log.w(TAG, "No picture size match the aspect ratio");
    506             for (Size size : sizes) {
    507                 if (optimalSize == null || size.width > optimalSize.width) {
    508                     optimalSize = size;
    509                 }
    510             }
    511         }
    512         return optimalSize;
    513     }
    514 
    515     public static void dumpParameters(Parameters parameters) {
    516         String flattened = parameters.flatten();
    517         StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
    518         Log.d(TAG, "Dump all camera parameters:");
    519         while (tokenizer.hasMoreElements()) {
    520             Log.d(TAG, tokenizer.nextToken());
    521         }
    522     }
    523 
    524     /**
    525      * Returns whether the device is voice-capable (meaning, it can do MMS).
    526      */
    527     public static boolean isMmsCapable(Context context) {
    528         TelephonyManager telephonyManager = (TelephonyManager)
    529                 context.getSystemService(Context.TELEPHONY_SERVICE);
    530         if (telephonyManager == null) {
    531             return false;
    532         }
    533 
    534         try {
    535             Class<?> partypes[] = new Class[0];
    536             Method sIsVoiceCapable = TelephonyManager.class.getMethod(
    537                     "isVoiceCapable", partypes);
    538 
    539             Object arglist[] = new Object[0];
    540             Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist);
    541             return (Boolean) retobj;
    542         } catch (java.lang.reflect.InvocationTargetException ite) {
    543             // Failure, must be another device.
    544             // Assume that it is voice capable.
    545         } catch (IllegalAccessException iae) {
    546             // Failure, must be an other device.
    547             // Assume that it is voice capable.
    548         } catch (NoSuchMethodException nsme) {
    549         }
    550         return true;
    551     }
    552 
    553     // This is for test only. Allow the camera to launch the specific camera.
    554     public static int getCameraFacingIntentExtras(Activity currentActivity) {
    555         int cameraId = -1;
    556 
    557         int intentCameraId =
    558                 currentActivity.getIntent().getIntExtra(Util.EXTRAS_CAMERA_FACING, -1);
    559 
    560         if (isFrontCameraIntent(intentCameraId)) {
    561             // Check if the front camera exist
    562             int frontCameraId = CameraHolder.instance().getFrontCameraId();
    563             if (frontCameraId != -1) {
    564                 cameraId = frontCameraId;
    565             }
    566         } else if (isBackCameraIntent(intentCameraId)) {
    567             // Check if the back camera exist
    568             int backCameraId = CameraHolder.instance().getBackCameraId();
    569             if (backCameraId != -1) {
    570                 cameraId = backCameraId;
    571             }
    572         }
    573         return cameraId;
    574     }
    575 
    576     private static boolean isFrontCameraIntent(int intentCameraId) {
    577         return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
    578     }
    579 
    580     private static boolean isBackCameraIntent(int intentCameraId) {
    581         return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
    582     }
    583 
    584     private static int sLocation[] = new int[2];
    585 
    586     // This method is not thread-safe.
    587     public static boolean pointInView(float x, float y, View v) {
    588         v.getLocationInWindow(sLocation);
    589         return x >= sLocation[0] && x < (sLocation[0] + v.getWidth())
    590                 && y >= sLocation[1] && y < (sLocation[1] + v.getHeight());
    591     }
    592 
    593     public static int[] getRelativeLocation(View reference, View view) {
    594         reference.getLocationInWindow(sLocation);
    595         int referenceX = sLocation[0];
    596         int referenceY = sLocation[1];
    597         view.getLocationInWindow(sLocation);
    598         sLocation[0] -= referenceX;
    599         sLocation[1] -= referenceY;
    600         return sLocation;
    601     }
    602 
    603     public static boolean isUriValid(Uri uri, ContentResolver resolver) {
    604         if (uri == null) return false;
    605 
    606         try {
    607             ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
    608             if (pfd == null) {
    609                 Log.e(TAG, "Fail to open URI. URI=" + uri);
    610                 return false;
    611             }
    612             pfd.close();
    613         } catch (IOException ex) {
    614             return false;
    615         }
    616         return true;
    617     }
    618 
    619     public static void viewUri(Uri uri, Context context) {
    620         if (!isUriValid(uri, context.getContentResolver())) {
    621             Log.e(TAG, "Uri invalid. uri=" + uri);
    622             return;
    623         }
    624 
    625         try {
    626             context.startActivity(new Intent(Util.REVIEW_ACTION, uri));
    627         } catch (ActivityNotFoundException ex) {
    628             try {
    629                 context.startActivity(new Intent(Intent.ACTION_VIEW, uri));
    630             } catch (ActivityNotFoundException e) {
    631                 Log.e(TAG, "review image fail. uri=" + uri, e);
    632             }
    633         }
    634     }
    635 
    636     public static void dumpRect(RectF rect, String msg) {
    637         Log.v(TAG, msg + "=(" + rect.left + "," + rect.top
    638                 + "," + rect.right + "," + rect.bottom + ")");
    639     }
    640 
    641     public static void rectFToRect(RectF rectF, Rect rect) {
    642         rect.left = Math.round(rectF.left);
    643         rect.top = Math.round(rectF.top);
    644         rect.right = Math.round(rectF.right);
    645         rect.bottom = Math.round(rectF.bottom);
    646     }
    647 
    648     public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
    649             int viewWidth, int viewHeight) {
    650         // Need mirror for front camera.
    651         matrix.setScale(mirror ? -1 : 1, 1);
    652         // This is the value for android.hardware.Camera.setDisplayOrientation.
    653         matrix.postRotate(displayOrientation);
    654         // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
    655         // UI coordinates range from (0, 0) to (width, height).
    656         matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
    657         matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
    658     }
    659 
    660     public static String createJpegName(long dateTaken) {
    661         synchronized (sImageFileNamer) {
    662             return sImageFileNamer.generateName(dateTaken);
    663         }
    664     }
    665 
    666     public static void broadcastNewPicture(Context context, Uri uri) {
    667         context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri));
    668         // Keep compatibility
    669         context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri));
    670     }
    671 
    672     public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) {
    673         if (view.getVisibility() == View.VISIBLE) return;
    674 
    675         view.setVisibility(View.VISIBLE);
    676         Animation animation = new AlphaAnimation(startAlpha, endAlpha);
    677         animation.setDuration(duration);
    678         view.startAnimation(animation);
    679     }
    680 
    681     public static void fadeIn(View view) {
    682         fadeIn(view, 0F, 1F, 400);
    683 
    684         // We disabled the button in fadeOut(), so enable it here.
    685         view.setEnabled(true);
    686     }
    687 
    688     public static void fadeOut(View view) {
    689         if (view.getVisibility() != View.VISIBLE) return;
    690 
    691         // Since the button is still clickable before fade-out animation
    692         // ends, we disable the button first to block click.
    693         view.setEnabled(false);
    694         Animation animation = new AlphaAnimation(1F, 0F);
    695         animation.setDuration(400);
    696         view.startAnimation(animation);
    697         view.setVisibility(View.GONE);
    698     }
    699 
    700     public static int getJpegRotation(int cameraId, int orientation) {
    701         // See android.hardware.Camera.Parameters.setRotation for
    702         // documentation.
    703         int rotation = 0;
    704         if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
    705             CameraInfo info = CameraHolder.instance().getCameraInfo()[cameraId];
    706             if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
    707                 rotation = (info.orientation - orientation + 360) % 360;
    708             } else {  // back-facing camera
    709                 rotation = (info.orientation + orientation) % 360;
    710             }
    711         }
    712         return rotation;
    713     }
    714 
    715     public static void setGpsParameters(Parameters parameters, Location loc) {
    716         // Clear previous GPS location from the parameters.
    717         parameters.removeGpsData();
    718 
    719         // We always encode GpsTimeStamp
    720         parameters.setGpsTimestamp(System.currentTimeMillis() / 1000);
    721 
    722         // Set GPS location.
    723         if (loc != null) {
    724             double lat = loc.getLatitude();
    725             double lon = loc.getLongitude();
    726             boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
    727 
    728             if (hasLatLon) {
    729                 Log.d(TAG, "Set gps location");
    730                 parameters.setGpsLatitude(lat);
    731                 parameters.setGpsLongitude(lon);
    732                 parameters.setGpsProcessingMethod(loc.getProvider().toUpperCase());
    733                 if (loc.hasAltitude()) {
    734                     parameters.setGpsAltitude(loc.getAltitude());
    735                 } else {
    736                     // for NETWORK_PROVIDER location provider, we may have
    737                     // no altitude information, but the driver needs it, so
    738                     // we fake one.
    739                     parameters.setGpsAltitude(0);
    740                 }
    741                 if (loc.getTime() != 0) {
    742                     // Location.getTime() is UTC in milliseconds.
    743                     // gps-timestamp is UTC in seconds.
    744                     long utcTimeSeconds = loc.getTime() / 1000;
    745                     parameters.setGpsTimestamp(utcTimeSeconds);
    746                 }
    747             } else {
    748                 loc = null;
    749             }
    750         }
    751     }
    752 
    753     private static class ImageFileNamer {
    754         private SimpleDateFormat mFormat;
    755 
    756         // The date (in milliseconds) used to generate the last name.
    757         private long mLastDate;
    758 
    759         // Number of names generated for the same second.
    760         private int mSameSecondCount;
    761 
    762         public ImageFileNamer(String format) {
    763             mFormat = new SimpleDateFormat(format);
    764         }
    765 
    766         public String generateName(long dateTaken) {
    767             Date date = new Date(dateTaken);
    768             String result = mFormat.format(date);
    769 
    770             // If the last name was generated for the same second,
    771             // we append _1, _2, etc to the name.
    772             if (dateTaken / 1000 == mLastDate / 1000) {
    773                 mSameSecondCount++;
    774                 result += "_" + mSameSecondCount;
    775             } else {
    776                 mLastDate = dateTaken;
    777                 mSameSecondCount = 0;
    778             }
    779 
    780             return result;
    781         }
    782     }
    783 }
    784