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