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 
isSupported(String value, List<String> supported)90     public static boolean isSupported(String value, List<String> supported) {
91         return supported == null ? false : supported.indexOf(value) >= 0;
92     }
93 
isAutoExposureLockSupported(Parameters params)94     public static boolean isAutoExposureLockSupported(Parameters params) {
95         return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
96     }
97 
isAutoWhiteBalanceLockSupported(Parameters params)98     public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
99         return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
100     }
101 
isVideoSnapshotSupported(Parameters params)102     public static boolean isVideoSnapshotSupported(Parameters params) {
103         return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED));
104     }
105 
isCameraHdrSupported(Parameters params)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)
isMeteringAreaSupported(Parameters params)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)
isFocusAreaSupported(Parameters params)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 
Util()136     private Util() {
137     }
138 
initialize(Context context)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 
dpToPixel(int dp)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.
rotate(Bitmap b, int degrees)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.
rotateAndMirror(Bitmap b, int degrees, boolean mirror)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      */
computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels)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 
computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels)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 
makeBitmap(byte[] jpegData, int maxNumOfPixels)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 
closeSilently(Closeable c)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 
Assert(boolean cond)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)
throwIfCameraDisabled(Activity activity)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 
openCamera(Activity activity, int cameraId)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 
showErrorAndFinish(final Activity activity, int msgId)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 
checkNotNull(T object)346     public static <T> T checkNotNull(T object) {
347         if (object == null) throw new NullPointerException();
348         return object;
349     }
350 
equals(Object a, Object b)351     public static boolean equals(Object a, Object b) {
352         return (a == b) || (a == null ? false : a.equals(b));
353     }
354 
nextPowerOf2(int n)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 
distance(float x, float y, float sx, float sy)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 
clamp(int x, int min, int max)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 
getDisplayRotation(Activity activity)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 
getDisplayOrientation(int degrees, int cameraId)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 
getCameraOrientation(int cameraId)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 
roundOrientation(int orientation, int orientationHistory)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)
getDefaultDisplaySize(Activity activity, Point size)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 
getOptimalPreviewSize(Activity currentActivity, List<Size> sizes, double targetRatio)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.
getOptimalVideoSnapshotPictureSize( List<Size> sizes, double targetRatio)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 
dumpParameters(Parameters parameters)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      */
isMmsCapable(Context context)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.
getCameraFacingIntentExtras(Activity currentActivity)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 
isFrontCameraIntent(int intentCameraId)569     private static boolean isFrontCameraIntent(int intentCameraId) {
570         return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
571     }
572 
isBackCameraIntent(int intentCameraId)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.
pointInView(float x, float y, View v)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 
getRelativeLocation(View reference, View view)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 
isUriValid(Uri uri, ContentResolver resolver)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 
viewUri(Uri uri, Context context)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 
dumpRect(RectF rect, String msg)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 
rectFToRect(RectF rectF, Rect rect)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 
prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation, int viewWidth, int viewHeight)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 
createJpegName(long dateTaken)653     public static String createJpegName(long dateTaken) {
654         synchronized (sImageFileNamer) {
655             return sImageFileNamer.generateName(dateTaken);
656         }
657     }
658 
broadcastNewPicture(Context context, Uri uri)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 
fadeIn(View view, float startAlpha, float endAlpha, long duration)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 
fadeIn(View view)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 
fadeOut(View view)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 
getJpegRotation(int cameraId, int orientation)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 
setGpsParameters(Parameters parameters, Location loc)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 
ImageFileNamer(String format)755         public ImageFileNamer(String format) {
756             mFormat = new SimpleDateFormat(format);
757         }
758 
generateName(long dateTaken)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