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