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