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