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.util;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.app.admin.DevicePolicyManager;
22import android.content.ActivityNotFoundException;
23import android.content.ComponentName;
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.Handler;
41import android.os.ParcelFileDescriptor;
42import android.telephony.TelephonyManager;
43import android.util.DisplayMetrics;
44import android.util.Log;
45import android.util.TypedValue;
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;
53import android.widget.Toast;
54
55import com.android.camera.CameraActivity;
56import com.android.camera.CameraDisabledException;
57import com.android.camera.CameraHolder;
58import com.android.camera.CameraManager;
59import com.android.camera.util.IntentHelper;
60import com.android.camera2.R;
61
62import java.io.Closeable;
63import java.io.IOException;
64import java.lang.reflect.Method;
65import java.text.SimpleDateFormat;
66import java.util.Date;
67import java.util.List;
68import java.util.Locale;
69import java.util.StringTokenizer;
70
71/**
72 * Collection of utility functions used in this package.
73 */
74public class CameraUtil {
75    private static final String TAG = "Util";
76
77    // For calculate the best fps range for still image capture.
78    private final static int MAX_PREVIEW_FPS_TIMES_1000 = 400000;
79    private final static int PREFERRED_PREVIEW_FPS_TIMES_1000 = 30000;
80
81    // For creating crop intents.
82    public static final String KEY_RETURN_DATA = "return-data";
83    public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked";
84
85    // Orientation hysteresis amount used in rounding, in degrees
86    public static final int ORIENTATION_HYSTERESIS = 5;
87
88    public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW";
89    // See android.hardware.Camera.ACTION_NEW_PICTURE.
90    public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
91    // See android.hardware.Camera.ACTION_NEW_VIDEO.
92    public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
93
94    // Broadcast Action: The camera application has become active in picture-taking mode.
95    public static final String ACTION_CAMERA_STARTED = "com.android.camera.action.CAMERA_STARTED";
96    // Broadcast Action: The camera application is no longer in active picture-taking mode.
97    public static final String ACTION_CAMERA_STOPPED = "com.android.camera.action.CAMERA_STOPPED";
98    // When the camera application is active in picture-taking mode, it listens for this intent,
99    // which upon receipt will trigger the shutter to capture a new picture, as if the user had
100    // pressed the shutter button.
101    public static final String ACTION_CAMERA_SHUTTER_CLICK =
102            "com.android.camera.action.SHUTTER_CLICK";
103
104    // Fields from android.hardware.Camera.Parameters
105    public static final String FOCUS_MODE_CONTINUOUS_PICTURE = "continuous-picture";
106    public static final String RECORDING_HINT = "recording-hint";
107    private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
108    private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported";
109    private static final String VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported";
110    public static final String SCENE_MODE_HDR = "hdr";
111    public static final String TRUE = "true";
112    public static final String FALSE = "false";
113
114    // Fields for the show-on-maps-functionality
115    private static final String MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
116    private static final String MAPS_CLASS_NAME = "com.google.android.maps.MapsActivity";
117
118    /** Has to be in sync with the receiving MovieActivity. */
119    public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back";
120
121    public static boolean isSupported(String value, List<String> supported) {
122        return supported == null ? false : supported.indexOf(value) >= 0;
123    }
124
125    public static boolean isAutoExposureLockSupported(Parameters params) {
126        return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
127    }
128
129    public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
130        return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
131    }
132
133    public static boolean isVideoSnapshotSupported(Parameters params) {
134        return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED));
135    }
136
137    public static boolean isCameraHdrSupported(Parameters params) {
138        List<String> supported = params.getSupportedSceneModes();
139        return (supported != null) && supported.contains(SCENE_MODE_HDR);
140    }
141
142    public static boolean isMeteringAreaSupported(Parameters params) {
143        return params.getMaxNumMeteringAreas() > 0;
144    }
145
146    public static boolean isFocusAreaSupported(Parameters params) {
147        return (params.getMaxNumFocusAreas() > 0
148                && isSupported(Parameters.FOCUS_MODE_AUTO,
149                        params.getSupportedFocusModes()));
150    }
151
152    // Private intent extras. Test only.
153    private static final String EXTRAS_CAMERA_FACING =
154            "android.intent.extras.CAMERA_FACING";
155
156    private static float sPixelDensity = 1;
157    private static ImageFileNamer sImageFileNamer;
158
159    private CameraUtil() {
160    }
161
162    public static void initialize(Context context) {
163        DisplayMetrics metrics = new DisplayMetrics();
164        WindowManager wm = (WindowManager)
165                context.getSystemService(Context.WINDOW_SERVICE);
166        wm.getDefaultDisplay().getMetrics(metrics);
167        sPixelDensity = metrics.density;
168        sImageFileNamer = new ImageFileNamer(
169                context.getString(R.string.image_file_name_format));
170    }
171
172    public static int dpToPixel(int dp) {
173        return Math.round(sPixelDensity * dp);
174    }
175
176    // Rotates the bitmap by the specified degree.
177    // If a new bitmap is created, the original bitmap is recycled.
178    public static Bitmap rotate(Bitmap b, int degrees) {
179        return rotateAndMirror(b, degrees, false);
180    }
181
182    // Rotates and/or mirrors the bitmap. If a new bitmap is created, the
183    // original bitmap is recycled.
184    public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) {
185        if ((degrees != 0 || mirror) && b != null) {
186            Matrix m = new Matrix();
187            // Mirror first.
188            // horizontal flip + rotation = -rotation + horizontal flip
189            if (mirror) {
190                m.postScale(-1, 1);
191                degrees = (degrees + 360) % 360;
192                if (degrees == 0 || degrees == 180) {
193                    m.postTranslate(b.getWidth(), 0);
194                } else if (degrees == 90 || degrees == 270) {
195                    m.postTranslate(b.getHeight(), 0);
196                } else {
197                    throw new IllegalArgumentException("Invalid degrees=" + degrees);
198                }
199            }
200            if (degrees != 0) {
201                // clockwise
202                m.postRotate(degrees,
203                        (float) b.getWidth() / 2, (float) b.getHeight() / 2);
204            }
205
206            try {
207                Bitmap b2 = Bitmap.createBitmap(
208                        b, 0, 0, b.getWidth(), b.getHeight(), m, true);
209                if (b != b2) {
210                    b.recycle();
211                    b = b2;
212                }
213            } catch (OutOfMemoryError ex) {
214                // We have no memory to rotate. Return the original bitmap.
215            }
216        }
217        return b;
218    }
219
220    /*
221     * Compute the sample size as a function of minSideLength
222     * and maxNumOfPixels.
223     * minSideLength is used to specify that minimal width or height of a
224     * bitmap.
225     * maxNumOfPixels is used to specify the maximal size in pixels that is
226     * tolerable in terms of memory usage.
227     *
228     * The function returns a sample size based on the constraints.
229     * Both size and minSideLength can be passed in as -1
230     * which indicates no care of the corresponding constraint.
231     * The functions prefers returning a sample size that
232     * generates a smaller bitmap, unless minSideLength = -1.
233     *
234     * Also, the function rounds up the sample size to a power of 2 or multiple
235     * of 8 because BitmapFactory only honors sample size this way.
236     * For example, BitmapFactory downsamples an image by 2 even though the
237     * request is 3. So we round up the sample size to avoid OOM.
238     */
239    public static int computeSampleSize(BitmapFactory.Options options,
240            int minSideLength, int maxNumOfPixels) {
241        int initialSize = computeInitialSampleSize(options, minSideLength,
242                maxNumOfPixels);
243
244        int roundedSize;
245        if (initialSize <= 8) {
246            roundedSize = 1;
247            while (roundedSize < initialSize) {
248                roundedSize <<= 1;
249            }
250        } else {
251            roundedSize = (initialSize + 7) / 8 * 8;
252        }
253
254        return roundedSize;
255    }
256
257    private static int computeInitialSampleSize(BitmapFactory.Options options,
258            int minSideLength, int maxNumOfPixels) {
259        double w = options.outWidth;
260        double h = options.outHeight;
261
262        int lowerBound = (maxNumOfPixels < 0) ? 1 :
263                (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
264        int upperBound = (minSideLength < 0) ? 128 :
265                (int) Math.min(Math.floor(w / minSideLength),
266                Math.floor(h / minSideLength));
267
268        if (upperBound < lowerBound) {
269            // return the larger one when there is no overlapping zone.
270            return lowerBound;
271        }
272
273        if (maxNumOfPixels < 0 && minSideLength < 0) {
274            return 1;
275        } else if (minSideLength < 0) {
276            return lowerBound;
277        } else {
278            return upperBound;
279        }
280    }
281
282    public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) {
283        try {
284            BitmapFactory.Options options = new BitmapFactory.Options();
285            options.inJustDecodeBounds = true;
286            BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
287                    options);
288            if (options.mCancel || options.outWidth == -1
289                    || options.outHeight == -1) {
290                return null;
291            }
292            options.inSampleSize = computeSampleSize(
293                    options, -1, maxNumOfPixels);
294            options.inJustDecodeBounds = false;
295
296            options.inDither = false;
297            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
298            return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
299                    options);
300        } catch (OutOfMemoryError ex) {
301            Log.e(TAG, "Got oom exception ", ex);
302            return null;
303        }
304    }
305
306    public static void closeSilently(Closeable c) {
307        if (c == null) return;
308        try {
309            c.close();
310        } catch (Throwable t) {
311            // do nothing
312        }
313    }
314
315    public static void Assert(boolean cond) {
316        if (!cond) {
317            throw new AssertionError();
318        }
319    }
320
321    private static void throwIfCameraDisabled(Activity activity) throws CameraDisabledException {
322        // Check if device policy has disabled the camera.
323        DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService(
324                Context.DEVICE_POLICY_SERVICE);
325        if (dpm.getCameraDisabled(null)) {
326            throw new CameraDisabledException();
327        }
328    }
329
330    public static CameraManager.CameraProxy openCamera(
331            Activity activity, final int cameraId,
332            Handler handler, final CameraManager.CameraOpenErrorCallback cb) {
333        try {
334            throwIfCameraDisabled(activity);
335            return CameraHolder.instance().open(handler, cameraId, cb);
336        } catch (CameraDisabledException ex) {
337            handler.post(new Runnable() {
338                @Override
339                public void run() {
340                    cb.onCameraDisabled(cameraId);
341                }
342            });
343        }
344        return null;
345    }
346
347    public static void showErrorAndFinish(final Activity activity, int msgId) {
348        DialogInterface.OnClickListener buttonListener =
349                new DialogInterface.OnClickListener() {
350            @Override
351            public void onClick(DialogInterface dialog, int which) {
352                activity.finish();
353            }
354        };
355        TypedValue out = new TypedValue();
356        activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true);
357        new AlertDialog.Builder(activity)
358                .setCancelable(false)
359                .setTitle(R.string.camera_error_title)
360                .setMessage(msgId)
361                .setNeutralButton(R.string.dialog_ok, buttonListener)
362                .setIcon(out.resourceId)
363                .show();
364    }
365
366    public static <T> T checkNotNull(T object) {
367        if (object == null) throw new NullPointerException();
368        return object;
369    }
370
371    public static boolean equals(Object a, Object b) {
372        return (a == b) || (a == null ? false : a.equals(b));
373    }
374
375    public static int nextPowerOf2(int n) {
376        n -= 1;
377        n |= n >>> 16;
378        n |= n >>> 8;
379        n |= n >>> 4;
380        n |= n >>> 2;
381        n |= n >>> 1;
382        return n + 1;
383    }
384
385    public static float distance(float x, float y, float sx, float sy) {
386        float dx = x - sx;
387        float dy = y - sy;
388        return (float) Math.sqrt(dx * dx + dy * dy);
389    }
390
391    public static int clamp(int x, int min, int max) {
392        if (x > max) return max;
393        if (x < min) return min;
394        return x;
395    }
396
397    public static float clamp(float x, float min, float max) {
398        if (x > max) return max;
399        if (x < min) return min;
400        return x;
401    }
402
403    public static int getDisplayRotation(Activity activity) {
404        int rotation = activity.getWindowManager().getDefaultDisplay()
405                .getRotation();
406        switch (rotation) {
407            case Surface.ROTATION_0: return 0;
408            case Surface.ROTATION_90: return 90;
409            case Surface.ROTATION_180: return 180;
410            case Surface.ROTATION_270: return 270;
411        }
412        return 0;
413    }
414
415    /**
416     * Calculate the default orientation of the device based on the width and
417     * height of the display when rotation = 0 (i.e. natural width and height)
418     * @param activity the activity context
419     * @return whether the default orientation of the device is portrait
420     */
421    public static boolean isDefaultToPortrait(Activity activity) {
422        Display currentDisplay = activity.getWindowManager().getDefaultDisplay();
423        Point displaySize = new Point();
424        currentDisplay.getSize(displaySize);
425        int orientation = currentDisplay.getRotation();
426        int naturalWidth, naturalHeight;
427        if (orientation == Surface.ROTATION_0 || orientation == Surface.ROTATION_180) {
428            naturalWidth = displaySize.x;
429            naturalHeight = displaySize.y;
430        } else {
431            naturalWidth = displaySize.y;
432            naturalHeight = displaySize.x;
433        }
434        return naturalWidth < naturalHeight;
435    }
436
437    public static int getDisplayOrientation(int degrees, int cameraId) {
438        // See android.hardware.Camera.setDisplayOrientation for
439        // documentation.
440        Camera.CameraInfo info = new Camera.CameraInfo();
441        Camera.getCameraInfo(cameraId, info);
442        int result;
443        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
444            result = (info.orientation + degrees) % 360;
445            result = (360 - result) % 360;  // compensate the mirror
446        } else {  // back-facing
447            result = (info.orientation - degrees + 360) % 360;
448        }
449        return result;
450    }
451
452    public static int getCameraOrientation(int cameraId) {
453        Camera.CameraInfo info = new Camera.CameraInfo();
454        Camera.getCameraInfo(cameraId, info);
455        return info.orientation;
456    }
457
458    public static int roundOrientation(int orientation, int orientationHistory) {
459        boolean changeOrientation = false;
460        if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
461            changeOrientation = true;
462        } else {
463            int dist = Math.abs(orientation - orientationHistory);
464            dist = Math.min( dist, 360 - dist );
465            changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS );
466        }
467        if (changeOrientation) {
468            return ((orientation + 45) / 90 * 90) % 360;
469        }
470        return orientationHistory;
471    }
472
473    private static Point getDefaultDisplaySize(Activity activity, Point size) {
474        activity.getWindowManager().getDefaultDisplay().getSize(size);
475        return size;
476    }
477
478    public static Size getOptimalPreviewSize(Activity currentActivity,
479            List<Size> sizes, double targetRatio) {
480
481        Point[] points = new Point[sizes.size()];
482
483        int index = 0;
484        for (Size s : sizes) {
485            points[index++] = new Point(s.width, s.height);
486        }
487
488        int optimalPickIndex = getOptimalPreviewSize(currentActivity, points, targetRatio);
489        return (optimalPickIndex == -1) ? null : sizes.get(optimalPickIndex);
490    }
491
492    public static int getOptimalPreviewSize(Activity currentActivity,
493            Point[] sizes, double targetRatio) {
494        // Use a very small tolerance because we want an exact match.
495        final double ASPECT_TOLERANCE = 0.01;
496        if (sizes == null) return -1;
497
498        int optimalSizeIndex = -1;
499        double minDiff = Double.MAX_VALUE;
500
501        // Because of bugs of overlay and layout, we sometimes will try to
502        // layout the viewfinder in the portrait orientation and thus get the
503        // wrong size of preview surface. When we change the preview size, the
504        // new overlay will be created before the old one closed, which causes
505        // an exception. For now, just get the screen size.
506        Point point = getDefaultDisplaySize(currentActivity, new Point());
507        int targetHeight = Math.min(point.x, point.y);
508        // Try to find an size match aspect ratio and size
509        for (int i = 0; i < sizes.length; i++) {
510            Point size = sizes[i];
511            double ratio = (double) size.x / size.y;
512            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
513            if (Math.abs(size.y - targetHeight) < minDiff) {
514                optimalSizeIndex = i;
515                minDiff = Math.abs(size.y - targetHeight);
516            }
517        }
518        // Cannot find the one match the aspect ratio. This should not happen.
519        // Ignore the requirement.
520        if (optimalSizeIndex == -1) {
521            Log.w(TAG, "No preview size match the aspect ratio");
522            minDiff = Double.MAX_VALUE;
523            for (int i = 0; i < sizes.length; i++) {
524                Point size = sizes[i];
525                if (Math.abs(size.y - targetHeight) < minDiff) {
526                    optimalSizeIndex = i;
527                    minDiff = Math.abs(size.y - targetHeight);
528                }
529            }
530        }
531        return optimalSizeIndex;
532    }
533
534    // Returns the largest picture size which matches the given aspect ratio.
535    public static Size getOptimalVideoSnapshotPictureSize(
536            List<Size> sizes, double targetRatio) {
537        // Use a very small tolerance because we want an exact match.
538        final double ASPECT_TOLERANCE = 0.001;
539        if (sizes == null) return null;
540
541        Size optimalSize = null;
542
543        // Try to find a size matches aspect ratio and has the largest width
544        for (Size size : sizes) {
545            double ratio = (double) size.width / size.height;
546            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
547            if (optimalSize == null || size.width > optimalSize.width) {
548                optimalSize = size;
549            }
550        }
551
552        // Cannot find one that matches the aspect ratio. This should not happen.
553        // Ignore the requirement.
554        if (optimalSize == null) {
555            Log.w(TAG, "No picture size match the aspect ratio");
556            for (Size size : sizes) {
557                if (optimalSize == null || size.width > optimalSize.width) {
558                    optimalSize = size;
559                }
560            }
561        }
562        return optimalSize;
563    }
564
565    public static void dumpParameters(Parameters parameters) {
566        String flattened = parameters.flatten();
567        StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
568        Log.d(TAG, "Dump all camera parameters:");
569        while (tokenizer.hasMoreElements()) {
570            Log.d(TAG, tokenizer.nextToken());
571        }
572    }
573
574    /**
575     * Returns whether the device is voice-capable (meaning, it can do MMS).
576     */
577    public static boolean isMmsCapable(Context context) {
578        TelephonyManager telephonyManager = (TelephonyManager)
579                context.getSystemService(Context.TELEPHONY_SERVICE);
580        if (telephonyManager == null) {
581            return false;
582        }
583
584        try {
585            Class<?> partypes[] = new Class[0];
586            Method sIsVoiceCapable = TelephonyManager.class.getMethod(
587                    "isVoiceCapable", partypes);
588
589            Object arglist[] = new Object[0];
590            Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist);
591            return (Boolean) retobj;
592        } catch (java.lang.reflect.InvocationTargetException ite) {
593            // Failure, must be another device.
594            // Assume that it is voice capable.
595        } catch (IllegalAccessException iae) {
596            // Failure, must be an other device.
597            // Assume that it is voice capable.
598        } catch (NoSuchMethodException nsme) {
599        }
600        return true;
601    }
602
603    // This is for test only. Allow the camera to launch the specific camera.
604    public static int getCameraFacingIntentExtras(Activity currentActivity) {
605        int cameraId = -1;
606
607        int intentCameraId =
608                currentActivity.getIntent().getIntExtra(CameraUtil.EXTRAS_CAMERA_FACING, -1);
609
610        if (isFrontCameraIntent(intentCameraId)) {
611            // Check if the front camera exist
612            int frontCameraId = CameraHolder.instance().getFrontCameraId();
613            if (frontCameraId != -1) {
614                cameraId = frontCameraId;
615            }
616        } else if (isBackCameraIntent(intentCameraId)) {
617            // Check if the back camera exist
618            int backCameraId = CameraHolder.instance().getBackCameraId();
619            if (backCameraId != -1) {
620                cameraId = backCameraId;
621            }
622        }
623        return cameraId;
624    }
625
626    private static boolean isFrontCameraIntent(int intentCameraId) {
627        return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
628    }
629
630    private static boolean isBackCameraIntent(int intentCameraId) {
631        return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
632    }
633
634    private static int sLocation[] = new int[2];
635
636    // This method is not thread-safe.
637    public static boolean pointInView(float x, float y, View v) {
638        v.getLocationInWindow(sLocation);
639        return x >= sLocation[0] && x < (sLocation[0] + v.getWidth())
640                && y >= sLocation[1] && y < (sLocation[1] + v.getHeight());
641    }
642
643    public static int[] getRelativeLocation(View reference, View view) {
644        reference.getLocationInWindow(sLocation);
645        int referenceX = sLocation[0];
646        int referenceY = sLocation[1];
647        view.getLocationInWindow(sLocation);
648        sLocation[0] -= referenceX;
649        sLocation[1] -= referenceY;
650        return sLocation;
651    }
652
653    public static boolean isUriValid(Uri uri, ContentResolver resolver) {
654        if (uri == null) return false;
655
656        try {
657            ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
658            if (pfd == null) {
659                Log.e(TAG, "Fail to open URI. URI=" + uri);
660                return false;
661            }
662            pfd.close();
663        } catch (IOException ex) {
664            return false;
665        }
666        return true;
667    }
668
669    public static void dumpRect(RectF rect, String msg) {
670        Log.v(TAG, msg + "=(" + rect.left + "," + rect.top
671                + "," + rect.right + "," + rect.bottom + ")");
672    }
673
674    public static void rectFToRect(RectF rectF, Rect rect) {
675        rect.left = Math.round(rectF.left);
676        rect.top = Math.round(rectF.top);
677        rect.right = Math.round(rectF.right);
678        rect.bottom = Math.round(rectF.bottom);
679    }
680
681    public static Rect rectFToRect(RectF rectF) {
682        Rect rect = new Rect();
683        rectFToRect(rectF, rect);
684        return rect;
685    }
686
687    public static RectF rectToRectF(Rect r) {
688        return new RectF(r.left, r.top, r.right, r.bottom);
689    }
690
691    public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
692            int viewWidth, int viewHeight) {
693        // Need mirror for front camera.
694        matrix.setScale(mirror ? -1 : 1, 1);
695        // This is the value for android.hardware.Camera.setDisplayOrientation.
696        matrix.postRotate(displayOrientation);
697        // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
698        // UI coordinates range from (0, 0) to (width, height).
699        matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
700        matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
701    }
702
703    public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
704                                     Rect previewRect) {
705        // Need mirror for front camera.
706        matrix.setScale(mirror ? -1 : 1, 1);
707        // This is the value for android.hardware.Camera.setDisplayOrientation.
708        matrix.postRotate(displayOrientation);
709
710        // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
711        // We need to map camera driver coordinates to preview rect coordinates
712        Matrix mapping = new Matrix();
713        mapping.setRectToRect(new RectF(-1000, -1000, 1000, 1000), rectToRectF(previewRect),
714                Matrix.ScaleToFit.FILL);
715        matrix.setConcat(mapping, matrix);
716    }
717
718    public static String createJpegName(long dateTaken) {
719        synchronized (sImageFileNamer) {
720            return sImageFileNamer.generateName(dateTaken);
721        }
722    }
723
724    public static void broadcastNewPicture(Context context, Uri uri) {
725        context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri));
726        // Keep compatibility
727        context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri));
728    }
729
730    public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) {
731        if (view.getVisibility() == View.VISIBLE) return;
732
733        view.setVisibility(View.VISIBLE);
734        Animation animation = new AlphaAnimation(startAlpha, endAlpha);
735        animation.setDuration(duration);
736        view.startAnimation(animation);
737    }
738
739    public static void fadeIn(View view) {
740        fadeIn(view, 0F, 1F, 400);
741
742        // We disabled the button in fadeOut(), so enable it here.
743        view.setEnabled(true);
744    }
745
746    public static void fadeOut(View view) {
747        if (view.getVisibility() != View.VISIBLE) return;
748
749        // Since the button is still clickable before fade-out animation
750        // ends, we disable the button first to block click.
751        view.setEnabled(false);
752        Animation animation = new AlphaAnimation(1F, 0F);
753        animation.setDuration(400);
754        view.startAnimation(animation);
755        view.setVisibility(View.GONE);
756    }
757
758    public static int getJpegRotation(int cameraId, int orientation) {
759        // See android.hardware.Camera.Parameters.setRotation for
760        // documentation.
761        int rotation = 0;
762        if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
763            CameraInfo info = CameraHolder.instance().getCameraInfo()[cameraId];
764            if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
765                rotation = (info.orientation - orientation + 360) % 360;
766            } else {  // back-facing camera
767                rotation = (info.orientation + orientation) % 360;
768            }
769        }
770        return rotation;
771    }
772
773    /**
774     * Down-samples a jpeg byte array.
775     * @param data a byte array of jpeg data
776     * @param downSampleFactor down-sample factor
777     * @return decoded and down-sampled bitmap
778     */
779    public static Bitmap downSample(final byte[] data, int downSampleFactor) {
780        final BitmapFactory.Options opts = new BitmapFactory.Options();
781        // Downsample the image
782        opts.inSampleSize = downSampleFactor;
783        return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
784    }
785
786    public static void setGpsParameters(Parameters parameters, Location loc) {
787        // Clear previous GPS location from the parameters.
788        parameters.removeGpsData();
789
790        // We always encode GpsTimeStamp
791        parameters.setGpsTimestamp(System.currentTimeMillis() / 1000);
792
793        // Set GPS location.
794        if (loc != null) {
795            double lat = loc.getLatitude();
796            double lon = loc.getLongitude();
797            boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
798
799            if (hasLatLon) {
800                Log.d(TAG, "Set gps location");
801                parameters.setGpsLatitude(lat);
802                parameters.setGpsLongitude(lon);
803                parameters.setGpsProcessingMethod(loc.getProvider().toUpperCase());
804                if (loc.hasAltitude()) {
805                    parameters.setGpsAltitude(loc.getAltitude());
806                } else {
807                    // for NETWORK_PROVIDER location provider, we may have
808                    // no altitude information, but the driver needs it, so
809                    // we fake one.
810                    parameters.setGpsAltitude(0);
811                }
812                if (loc.getTime() != 0) {
813                    // Location.getTime() is UTC in milliseconds.
814                    // gps-timestamp is UTC in seconds.
815                    long utcTimeSeconds = loc.getTime() / 1000;
816                    parameters.setGpsTimestamp(utcTimeSeconds);
817                }
818            } else {
819                loc = null;
820            }
821        }
822    }
823
824    /**
825     * For still image capture, we need to get the right fps range such that the
826     * camera can slow down the framerate to allow for less-noisy/dark
827     * viewfinder output in dark conditions.
828     *
829     * @param params Camera's parameters.
830     * @return null if no appropiate fps range can't be found. Otherwise, return
831     *         the right range.
832     */
833    public static int[] getPhotoPreviewFpsRange(Parameters params) {
834        return getPhotoPreviewFpsRange(params.getSupportedPreviewFpsRange());
835    }
836
837    public static int[] getPhotoPreviewFpsRange(List<int[]> frameRates) {
838        if (frameRates.size() == 0) {
839            Log.e(TAG, "No suppoted frame rates returned!");
840            return null;
841        }
842
843        // Find the lowest min rate in supported ranges who can cover 30fps.
844        int lowestMinRate = MAX_PREVIEW_FPS_TIMES_1000;
845        for (int[] rate : frameRates) {
846            int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX];
847            int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX];
848            if (maxFps >= PREFERRED_PREVIEW_FPS_TIMES_1000 &&
849                    minFps <= PREFERRED_PREVIEW_FPS_TIMES_1000 &&
850                    minFps < lowestMinRate) {
851                lowestMinRate = minFps;
852            }
853        }
854
855        // Find all the modes with the lowest min rate found above, the pick the
856        // one with highest max rate.
857        int resultIndex = -1;
858        int highestMaxRate = 0;
859        for (int i = 0; i < frameRates.size(); i++) {
860            int[] rate = frameRates.get(i);
861            int minFps = rate[Parameters.PREVIEW_FPS_MIN_INDEX];
862            int maxFps = rate[Parameters.PREVIEW_FPS_MAX_INDEX];
863            if (minFps == lowestMinRate && highestMaxRate < maxFps) {
864                highestMaxRate = maxFps;
865                resultIndex = i;
866            }
867        }
868
869        if (resultIndex >= 0) {
870            return frameRates.get(resultIndex);
871        }
872        Log.e(TAG, "Can't find an appropiate frame rate range!");
873        return null;
874    }
875
876    public static int[] getMaxPreviewFpsRange(Parameters params) {
877        List<int[]> frameRates = params.getSupportedPreviewFpsRange();
878        if (frameRates != null && frameRates.size() > 0) {
879            // The list is sorted. Return the last element.
880            return frameRates.get(frameRates.size() - 1);
881        }
882        return new int[0];
883    }
884
885    private static class ImageFileNamer {
886        private final SimpleDateFormat mFormat;
887
888        // The date (in milliseconds) used to generate the last name.
889        private long mLastDate;
890
891        // Number of names generated for the same second.
892        private int mSameSecondCount;
893
894        public ImageFileNamer(String format) {
895            mFormat = new SimpleDateFormat(format);
896        }
897
898        public String generateName(long dateTaken) {
899            Date date = new Date(dateTaken);
900            String result = mFormat.format(date);
901
902            // If the last name was generated for the same second,
903            // we append _1, _2, etc to the name.
904            if (dateTaken / 1000 == mLastDate / 1000) {
905                mSameSecondCount++;
906                result += "_" + mSameSecondCount;
907            } else {
908                mLastDate = dateTaken;
909                mSameSecondCount = 0;
910            }
911
912            return result;
913        }
914    }
915
916    public static void playVideo(Activity activity, Uri uri, String title) {
917        try {
918            boolean isSecureCamera = ((CameraActivity)activity).isSecureCamera();
919            UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
920                    UsageStatistics.ACTION_PLAY_VIDEO, null);
921            if (!isSecureCamera) {
922                Intent intent = IntentHelper.getVideoPlayerIntent(activity, uri)
923                        .putExtra(Intent.EXTRA_TITLE, title)
924                        .putExtra(KEY_TREAT_UP_AS_BACK, true);
925                activity.startActivityForResult(intent, CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
926            } else {
927                // In order not to send out any intent to be intercepted and
928                // show the lock screen immediately, we just let the secure
929                // camera activity finish.
930                activity.finish();
931            }
932        } catch (ActivityNotFoundException e) {
933            Toast.makeText(activity, activity.getString(R.string.video_err),
934                    Toast.LENGTH_SHORT).show();
935        }
936    }
937
938    /**
939     * Starts GMM with the given location shown. If this fails, and GMM could
940     * not be found, we use a geo intent as a fallback.
941     *
942     * @param activity the activity to use for launching the Maps intent.
943     * @param latLong a 2-element array containing {latitude/longitude}.
944     */
945    public static void showOnMap(Activity activity, double[] latLong) {
946        try {
947            // We don't use "geo:latitude,longitude" because it only centers
948            // the MapView to the specified location, but we need a marker
949            // for further operations (routing to/from).
950            // The q=(lat, lng) syntax is suggested by geo-team.
951            String uri = String.format(Locale.ENGLISH, "http://maps.google.com/maps?f=q&q=(%f,%f)",
952                    latLong[0], latLong[1]);
953            ComponentName compName = new ComponentName(MAPS_PACKAGE_NAME,
954                    MAPS_CLASS_NAME);
955            Intent mapsIntent = new Intent(Intent.ACTION_VIEW,
956                    Uri.parse(uri)).setComponent(compName);
957            activity.startActivityForResult(mapsIntent,
958                    CameraActivity.REQ_CODE_DONT_SWITCH_TO_PREVIEW);
959        } catch (ActivityNotFoundException e) {
960            // Use the "geo intent" if no GMM is installed
961            Log.e(TAG, "GMM activity not found!", e);
962            String url = String.format(Locale.ENGLISH, "geo:%f,%f", latLong[0], latLong[1]);
963            Intent mapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
964            activity.startActivity(mapsIntent);
965        }
966    }
967
968    /**
969     * Dumps the stack trace.
970     *
971     * @param level How many levels of the stack are dumped. 0 means all.
972     * @return A {@link java.lang.String} of all the output with newline
973     * between each.
974     */
975    public static String dumpStackTrace(int level) {
976        StackTraceElement[] elems = Thread.currentThread().getStackTrace();
977        // Ignore the first 3 elements.
978        level = (level == 0 ? elems.length : Math.min(level + 3, elems.length));
979        String ret = new String();
980        for (int i = 3; i < level; i++) {
981            ret = ret + "\t" + elems[i].toString() + '\n';
982        }
983        return ret;
984    }
985}
986