Util.java revision ea136afa2f4d38428ad486df5fb0a24db8314a3d
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.Parameters;
32import android.hardware.Camera.Size;
33import android.net.Uri;
34import android.os.Build;
35import android.os.ParcelFileDescriptor;
36import android.telephony.TelephonyManager;
37import android.util.DisplayMetrics;
38import android.util.Log;
39import android.view.Display;
40import android.view.Surface;
41import android.view.View;
42import android.view.WindowManager;
43import android.view.animation.AlphaAnimation;
44import android.view.animation.Animation;
45
46import java.io.Closeable;
47import java.io.IOException;
48import java.lang.reflect.Method;
49import java.text.SimpleDateFormat;
50import java.util.Date;
51import java.util.List;
52import java.util.StringTokenizer;
53
54/**
55 * Collection of utility functions used in this package.
56 */
57public class Util {
58    private static final String TAG = "Util";
59    private static final int DIRECTION_LEFT = 0;
60    private static final int DIRECTION_RIGHT = 1;
61    private static final int DIRECTION_UP = 2;
62    private static final int DIRECTION_DOWN = 3;
63
64    public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW";
65
66    // Private intent extras. Test only.
67    private static final String EXTRAS_CAMERA_FACING =
68            "android.intent.extras.CAMERA_FACING";
69
70    private static boolean sIsTabletUI;
71    private static float sPixelDensity = 1;
72    private static String sImageFileNameFormat;
73
74    private Util() {
75    }
76
77    public static void initialize(Context context) {
78        sIsTabletUI = (context.getResources().getConfiguration().screenWidthDp >= 1024);
79
80        DisplayMetrics metrics = new DisplayMetrics();
81        WindowManager wm = (WindowManager)
82                context.getSystemService(Context.WINDOW_SERVICE);
83        wm.getDefaultDisplay().getMetrics(metrics);
84        sPixelDensity = metrics.density;
85
86        sImageFileNameFormat = context.getString(R.string.image_file_name_format);
87    }
88
89    public static boolean isTabletUI() {
90        return sIsTabletUI;
91    }
92
93    public static int dpToPixel(int dp) {
94        return Math.round(sPixelDensity * dp);
95    }
96
97    // Rotates the bitmap by the specified degree.
98    // If a new bitmap is created, the original bitmap is recycled.
99    public static Bitmap rotate(Bitmap b, int degrees) {
100        return rotateAndMirror(b, degrees, false);
101    }
102
103    // Rotates and/or mirrors the bitmap. If a new bitmap is created, the
104    // original bitmap is recycled.
105    public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) {
106        if ((degrees != 0 || mirror) && b != null) {
107            Matrix m = new Matrix();
108            m.setRotate(degrees,
109                    (float) b.getWidth() / 2, (float) b.getHeight() / 2);
110            if (mirror) {
111                m.postScale(-1, 1);
112                degrees = (degrees + 360) % 360;
113                if (degrees == 0 || degrees == 180) {
114                    m.postTranslate((float) b.getWidth(), 0);
115                } else if (degrees == 90 || degrees == 270) {
116                    m.postTranslate((float) b.getHeight(), 0);
117                } else {
118                    throw new IllegalArgumentException("Invalid degrees=" + degrees);
119                }
120            }
121
122            try {
123                Bitmap b2 = Bitmap.createBitmap(
124                        b, 0, 0, b.getWidth(), b.getHeight(), m, true);
125                if (b != b2) {
126                    b.recycle();
127                    b = b2;
128                }
129            } catch (OutOfMemoryError ex) {
130                // We have no memory to rotate. Return the original bitmap.
131            }
132        }
133        return b;
134    }
135
136    /*
137     * Compute the sample size as a function of minSideLength
138     * and maxNumOfPixels.
139     * minSideLength is used to specify that minimal width or height of a
140     * bitmap.
141     * maxNumOfPixels is used to specify the maximal size in pixels that is
142     * tolerable in terms of memory usage.
143     *
144     * The function returns a sample size based on the constraints.
145     * Both size and minSideLength can be passed in as -1
146     * which indicates no care of the corresponding constraint.
147     * The functions prefers returning a sample size that
148     * generates a smaller bitmap, unless minSideLength = -1.
149     *
150     * Also, the function rounds up the sample size to a power of 2 or multiple
151     * of 8 because BitmapFactory only honors sample size this way.
152     * For example, BitmapFactory downsamples an image by 2 even though the
153     * request is 3. So we round up the sample size to avoid OOM.
154     */
155    public static int computeSampleSize(BitmapFactory.Options options,
156            int minSideLength, int maxNumOfPixels) {
157        int initialSize = computeInitialSampleSize(options, minSideLength,
158                maxNumOfPixels);
159
160        int roundedSize;
161        if (initialSize <= 8) {
162            roundedSize = 1;
163            while (roundedSize < initialSize) {
164                roundedSize <<= 1;
165            }
166        } else {
167            roundedSize = (initialSize + 7) / 8 * 8;
168        }
169
170        return roundedSize;
171    }
172
173    private static int computeInitialSampleSize(BitmapFactory.Options options,
174            int minSideLength, int maxNumOfPixels) {
175        double w = options.outWidth;
176        double h = options.outHeight;
177
178        int lowerBound = (maxNumOfPixels < 0) ? 1 :
179                (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
180        int upperBound = (minSideLength < 0) ? 128 :
181                (int) Math.min(Math.floor(w / minSideLength),
182                Math.floor(h / minSideLength));
183
184        if (upperBound < lowerBound) {
185            // return the larger one when there is no overlapping zone.
186            return lowerBound;
187        }
188
189        if (maxNumOfPixels < 0 && minSideLength < 0) {
190            return 1;
191        } else if (minSideLength < 0) {
192            return lowerBound;
193        } else {
194            return upperBound;
195        }
196    }
197
198    public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) {
199        try {
200            BitmapFactory.Options options = new BitmapFactory.Options();
201            options.inJustDecodeBounds = true;
202            BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
203                    options);
204            if (options.mCancel || options.outWidth == -1
205                    || options.outHeight == -1) {
206                return null;
207            }
208            options.inSampleSize = computeSampleSize(
209                    options, -1, maxNumOfPixels);
210            options.inJustDecodeBounds = false;
211
212            options.inDither = false;
213            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
214            return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
215                    options);
216        } catch (OutOfMemoryError ex) {
217            Log.e(TAG, "Got oom exception ", ex);
218            return null;
219        }
220    }
221
222    public static void closeSilently(Closeable c) {
223        if (c == null) return;
224        try {
225            c.close();
226        } catch (Throwable t) {
227            // do nothing
228        }
229    }
230
231    public static void Assert(boolean cond) {
232        if (!cond) {
233            throw new AssertionError();
234        }
235    }
236
237    public static android.hardware.Camera openCamera(Activity activity, int cameraId)
238            throws CameraHardwareException, CameraDisabledException {
239        // Check if device policy has disabled the camera.
240        DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService(
241                Context.DEVICE_POLICY_SERVICE);
242        if (dpm.getCameraDisabled(null)) {
243            throw new CameraDisabledException();
244        }
245
246        try {
247            return CameraHolder.instance().open(cameraId);
248        } catch (CameraHardwareException e) {
249            // In eng build, we throw the exception so that test tool
250            // can detect it and report it
251            if ("eng".equals(Build.TYPE)) {
252                throw new RuntimeException("openCamera failed", e);
253            } else {
254                throw e;
255            }
256        }
257    }
258
259    public static void showErrorAndFinish(final Activity activity, int msgId) {
260        DialogInterface.OnClickListener buttonListener =
261                new DialogInterface.OnClickListener() {
262            public void onClick(DialogInterface dialog, int which) {
263                activity.finish();
264            }
265        };
266        new AlertDialog.Builder(activity)
267                .setCancelable(false)
268                .setIconAttribute(android.R.attr.alertDialogIcon)
269                .setTitle(R.string.camera_error_title)
270                .setMessage(msgId)
271                .setNeutralButton(R.string.dialog_ok, buttonListener)
272                .show();
273    }
274
275    public static <T> T checkNotNull(T object) {
276        if (object == null) throw new NullPointerException();
277        return object;
278    }
279
280    public static boolean equals(Object a, Object b) {
281        return (a == b) || (a == null ? false : a.equals(b));
282    }
283
284    public static int nextPowerOf2(int n) {
285        n -= 1;
286        n |= n >>> 16;
287        n |= n >>> 8;
288        n |= n >>> 4;
289        n |= n >>> 2;
290        n |= n >>> 1;
291        return n + 1;
292    }
293
294    public static float distance(float x, float y, float sx, float sy) {
295        float dx = x - sx;
296        float dy = y - sy;
297        return (float) Math.sqrt(dx * dx + dy * dy);
298    }
299
300    public static int clamp(int x, int min, int max) {
301        if (x > max) return max;
302        if (x < min) return min;
303        return x;
304    }
305
306    public static int getDisplayRotation(Activity activity) {
307        int rotation = activity.getWindowManager().getDefaultDisplay()
308                .getRotation();
309        switch (rotation) {
310            case Surface.ROTATION_0: return 0;
311            case Surface.ROTATION_90: return 90;
312            case Surface.ROTATION_180: return 180;
313            case Surface.ROTATION_270: return 270;
314        }
315        return 0;
316    }
317
318    public static int getDisplayOrientation(int degrees, int cameraId) {
319        // See android.hardware.Camera.setDisplayOrientation for
320        // documentation.
321        Camera.CameraInfo info = new Camera.CameraInfo();
322        Camera.getCameraInfo(cameraId, info);
323        int result;
324        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
325            result = (info.orientation + degrees) % 360;
326            result = (360 - result) % 360;  // compensate the mirror
327        } else {  // back-facing
328            result = (info.orientation - degrees + 360) % 360;
329        }
330        return result;
331    }
332
333    public static Size getOptimalPreviewSize(Activity currentActivity,
334            List<Size> sizes, double targetRatio) {
335        // Use a very small tolerance because we want an exact match.
336        final double ASPECT_TOLERANCE = 0.001;
337        if (sizes == null) return null;
338
339        Size optimalSize = null;
340        double minDiff = Double.MAX_VALUE;
341
342        // Because of bugs of overlay and layout, we sometimes will try to
343        // layout the viewfinder in the portrait orientation and thus get the
344        // wrong size of mSurfaceView. When we change the preview size, the
345        // new overlay will be created before the old one closed, which causes
346        // an exception. For now, just get the screen size
347
348        Display display = currentActivity.getWindowManager().getDefaultDisplay();
349        int targetHeight = Math.min(display.getHeight(), display.getWidth());
350
351        if (targetHeight <= 0) {
352            // We don't know the size of SurfaceView, use screen height
353            targetHeight = display.getHeight();
354        }
355
356        // Try to find an size match aspect ratio and size
357        for (Size size : sizes) {
358            double ratio = (double) size.width / size.height;
359            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
360            if (Math.abs(size.height - targetHeight) < minDiff) {
361                optimalSize = size;
362                minDiff = Math.abs(size.height - targetHeight);
363            }
364        }
365
366        // Cannot find the one match the aspect ratio. This should not happen.
367        // Ignore the requirement.
368        if (optimalSize == null) {
369            Log.w(TAG, "No preview size match the aspect ratio");
370            minDiff = Double.MAX_VALUE;
371            for (Size size : sizes) {
372                if (Math.abs(size.height - targetHeight) < minDiff) {
373                    optimalSize = size;
374                    minDiff = Math.abs(size.height - targetHeight);
375                }
376            }
377        }
378        return optimalSize;
379    }
380
381    public static void dumpParameters(Parameters parameters) {
382        String flattened = parameters.flatten();
383        StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
384        Log.d(TAG, "Dump all camera parameters:");
385        while (tokenizer.hasMoreElements()) {
386            Log.d(TAG, tokenizer.nextToken());
387        }
388    }
389
390    /**
391     * Returns whether the device is voice-capable (meaning, it can do MMS).
392     */
393    public static boolean isMmsCapable(Context context) {
394        TelephonyManager telephonyManager = (TelephonyManager)
395                context.getSystemService(Context.TELEPHONY_SERVICE);
396        if (telephonyManager == null) {
397            return false;
398        }
399
400        try {
401            Class partypes[] = new Class[0];
402            Method sIsVoiceCapable = TelephonyManager.class.getMethod(
403                    "isVoiceCapable", partypes);
404
405            Object arglist[] = new Object[0];
406            Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist);
407            return (Boolean) retobj;
408        } catch (java.lang.reflect.InvocationTargetException ite) {
409            // Failure, must be another device.
410            // Assume that it is voice capable.
411        } catch (IllegalAccessException iae) {
412            // Failure, must be an other device.
413            // Assume that it is voice capable.
414        } catch (NoSuchMethodException nsme) {
415        }
416        return true;
417    }
418
419    // This is for test only. Allow the camera to launch the specific camera.
420    public static int getCameraFacingIntentExtras(Activity currentActivity) {
421        int cameraId = -1;
422
423        int intentCameraId =
424                currentActivity.getIntent().getIntExtra(Util.EXTRAS_CAMERA_FACING, -1);
425
426        if (isFrontCameraIntent(intentCameraId)) {
427            // Check if the front camera exist
428            int frontCameraId = CameraHolder.instance().getFrontCameraId();
429            if (frontCameraId != -1) {
430                cameraId = frontCameraId;
431            }
432        } else if (isBackCameraIntent(intentCameraId)) {
433            // Check if the back camera exist
434            int backCameraId = CameraHolder.instance().getBackCameraId();
435            if (backCameraId != -1) {
436                cameraId = backCameraId;
437            }
438        }
439        return cameraId;
440    }
441
442    private static boolean isFrontCameraIntent(int intentCameraId) {
443        return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
444    }
445
446    private static boolean isBackCameraIntent(int intentCameraId) {
447        return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
448    }
449
450    private static int mLocation[] = new int[2];
451
452    // This method is not thread-safe.
453    public static boolean pointInView(float x, float y, View v) {
454        v.getLocationInWindow(mLocation);
455        return x >= mLocation[0] && x < (mLocation[0] + v.getWidth())
456                && y >= mLocation[1] && y < (mLocation[1] + v.getHeight());
457    }
458
459    public static boolean isUriValid(Uri uri, ContentResolver resolver) {
460        if (uri == null) return false;
461
462        try {
463            ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
464            if (pfd == null) {
465                Log.e(TAG, "Fail to open URI. URI=" + uri);
466                return false;
467            }
468            pfd.close();
469        } catch (IOException ex) {
470            return false;
471        }
472        return true;
473    }
474
475    public static void viewUri(Uri uri, Context context) {
476        if (!isUriValid(uri, context.getContentResolver())) {
477            Log.e(TAG, "Uri invalid. uri=" + uri);
478            return;
479        }
480
481        try {
482            context.startActivity(new Intent(Util.REVIEW_ACTION, uri));
483        } catch (ActivityNotFoundException ex) {
484            try {
485                context.startActivity(new Intent(Intent.ACTION_VIEW, uri));
486            } catch (ActivityNotFoundException e) {
487                Log.e(TAG, "review image fail. uri=" + uri, e);
488            }
489        }
490    }
491
492    public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
493            int viewWidth, int viewHeight) {
494        // Need mirror for front camera.
495        matrix.setScale(mirror ? -1 : 1, 1);
496        // This is the value for android.hardware.Camera.setDisplayOrientation.
497        matrix.postRotate(displayOrientation);
498        // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
499        // UI coordinates range from (0, 0) to (width, height).
500        matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
501        matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
502    }
503
504    public static String createJpegName(long dateTaken) {
505        Date date = new Date(dateTaken);
506        SimpleDateFormat dateFormat = new SimpleDateFormat(sImageFileNameFormat);
507        return dateFormat.format(date);
508    }
509
510    public static void broadcastNewPicture(Context context, Uri uri) {
511        context.sendBroadcast(new Intent(android.hardware.Camera.ACTION_NEW_PICTURE, uri));
512        // Keep compatibility
513        context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri));
514    }
515
516    public static void fadeIn(View view) {
517        view.setVisibility(View.VISIBLE);
518        Animation animation = new AlphaAnimation(0F, 1F);
519        animation.setDuration(500);
520        view.startAnimation(animation);
521    }
522}
523