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