/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.camera; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.hardware.Camera; import android.hardware.Camera.Parameters; import android.hardware.Camera.Size; import android.telephony.TelephonyManager; import android.util.Log; import android.view.Display; import android.view.Surface; import android.view.View; import android.view.animation.Animation; import android.view.animation.TranslateAnimation; import java.io.Closeable; import java.lang.reflect.Method; import java.util.List; import java.util.StringTokenizer; /** * Collection of utility functions used in this package. */ public class Util { private static final String TAG = "Util"; public static final int DIRECTION_LEFT = 0; public static final int DIRECTION_RIGHT = 1; public static final int DIRECTION_UP = 2; public static final int DIRECTION_DOWN = 3; public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW"; // Private intent extras. Test only. public static final String EXTRAS_CAMERA_FACING = "android.intent.extras.CAMERA_FACING"; private Util() { } // Rotates the bitmap by the specified degree. // If a new bitmap is created, the original bitmap is recycled. public static Bitmap rotate(Bitmap b, int degrees) { return rotateAndMirror(b, degrees, false); } // Rotates and/or mirrors the bitmap. If a new bitmap is created, the // original bitmap is recycled. public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) { if ((degrees != 0 || mirror) && b != null) { Matrix m = new Matrix(); m.setRotate(degrees, (float) b.getWidth() / 2, (float) b.getHeight() / 2); if (mirror) { m.postScale(-1, 1); degrees = (degrees + 360) % 360; if (degrees == 0 || degrees == 180) { m.postTranslate((float) b.getWidth(), 0); } else if (degrees == 90 || degrees == 270) { m.postTranslate((float) b.getHeight(), 0); } else { throw new IllegalArgumentException("Invalid degrees=" + degrees); } } try { Bitmap b2 = Bitmap.createBitmap( b, 0, 0, b.getWidth(), b.getHeight(), m, true); if (b != b2) { b.recycle(); b = b2; } } catch (OutOfMemoryError ex) { // We have no memory to rotate. Return the original bitmap. } } return b; } /* * Compute the sample size as a function of minSideLength * and maxNumOfPixels. * minSideLength is used to specify that minimal width or height of a * bitmap. * maxNumOfPixels is used to specify the maximal size in pixels that is * tolerable in terms of memory usage. * * The function returns a sample size based on the constraints. * Both size and minSideLength can be passed in as -1 * which indicates no care of the corresponding constraint. * The functions prefers returning a sample size that * generates a smaller bitmap, unless minSideLength = -1. * * Also, the function rounds up the sample size to a power of 2 or multiple * of 8 because BitmapFactory only honors sample size this way. * For example, BitmapFactory downsamples an image by 2 even though the * request is 3. So we round up the sample size to avoid OOM. */ public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) { int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels); int roundedSize; if (initialSize <= 8) { roundedSize = 1; while (roundedSize < initialSize) { roundedSize <<= 1; } } else { roundedSize = (initialSize + 7) / 8 * 8; } return roundedSize; } private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) { double w = options.outWidth; double h = options.outHeight; int lowerBound = (maxNumOfPixels < 0) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels)); int upperBound = (minSideLength < 0) ? 128 : (int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength)); if (upperBound < lowerBound) { // return the larger one when there is no overlapping zone. return lowerBound; } if (maxNumOfPixels < 0 && minSideLength < 0) { return 1; } else if (minSideLength < 0) { return lowerBound; } else { return upperBound; } } public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) { try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, options); if (options.mCancel || options.outWidth == -1 || options.outHeight == -1) { return null; } options.inSampleSize = computeSampleSize( options, -1, maxNumOfPixels); options.inJustDecodeBounds = false; options.inDither = false; options.inPreferredConfig = Bitmap.Config.ARGB_8888; return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, options); } catch (OutOfMemoryError ex) { Log.e(TAG, "Got oom exception ", ex); return null; } } public static void closeSilently(Closeable c) { if (c == null) return; try { c.close(); } catch (Throwable t) { // do nothing } } public static void Assert(boolean cond) { if (!cond) { throw new AssertionError(); } } public static void showFatalErrorAndFinish( final Activity activity, String title, String message) { DialogInterface.OnClickListener buttonListener = new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { activity.finish(); } }; new AlertDialog.Builder(activity) .setCancelable(false) .setIconAttribute(android.R.attr.alertDialogIcon) .setTitle(title) .setMessage(message) .setNeutralButton(R.string.details_ok, buttonListener) .show(); } public static Animation slideOut(View view, int to) { view.setVisibility(View.INVISIBLE); Animation anim; switch (to) { case DIRECTION_LEFT: anim = new TranslateAnimation(0, -view.getWidth(), 0, 0); break; case DIRECTION_RIGHT: anim = new TranslateAnimation(0, view.getWidth(), 0, 0); break; case DIRECTION_UP: anim = new TranslateAnimation(0, 0, 0, -view.getHeight()); break; case DIRECTION_DOWN: anim = new TranslateAnimation(0, 0, 0, view.getHeight()); break; default: throw new IllegalArgumentException(Integer.toString(to)); } anim.setDuration(500); view.startAnimation(anim); return anim; } public static Animation slideIn(View view, int from) { view.setVisibility(View.VISIBLE); Animation anim; switch (from) { case DIRECTION_LEFT: anim = new TranslateAnimation(-view.getWidth(), 0, 0, 0); break; case DIRECTION_RIGHT: anim = new TranslateAnimation(view.getWidth(), 0, 0, 0); break; case DIRECTION_UP: anim = new TranslateAnimation(0, 0, -view.getHeight(), 0); break; case DIRECTION_DOWN: anim = new TranslateAnimation(0, 0, view.getHeight(), 0); break; default: throw new IllegalArgumentException(Integer.toString(from)); } anim.setDuration(500); view.startAnimation(anim); return anim; } public static T checkNotNull(T object) { if (object == null) throw new NullPointerException(); return object; } public static boolean equals(Object a, Object b) { return (a == b) || (a == null ? false : a.equals(b)); } public static boolean isPowerOf2(int n) { return (n & -n) == n; } public static int nextPowerOf2(int n) { n -= 1; n |= n >>> 16; n |= n >>> 8; n |= n >>> 4; n |= n >>> 2; n |= n >>> 1; return n + 1; } public static float distance(float x, float y, float sx, float sy) { float dx = x - sx; float dy = y - sy; return (float) Math.sqrt(dx * dx + dy * dy); } public static int clamp(int x, int min, int max) { if (x > max) return max; if (x < min) return min; return x; } public static int getDisplayRotation(Activity activity) { int rotation = activity.getWindowManager().getDefaultDisplay() .getRotation(); switch (rotation) { case Surface.ROTATION_0: return 0; case Surface.ROTATION_90: return 90; case Surface.ROTATION_180: return 180; case Surface.ROTATION_270: return 270; } return 0; } public static void setCameraDisplayOrientation(int degrees, int cameraId, Camera camera) { // See android.hardware.Camera.setCameraDisplayOrientation for // documentation. Camera.CameraInfo info = new Camera.CameraInfo(); Camera.getCameraInfo(cameraId, info); int result; if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { result = (info.orientation + degrees) % 360; result = (360 - result) % 360; // compensate the mirror } else { // back-facing result = (info.orientation - degrees + 360) % 360; } camera.setDisplayOrientation(result); } public static Size getOptimalPreviewSize(Activity currentActivity, List sizes, double targetRatio) { // Use a very small tolerance because we want an exact match. final double ASPECT_TOLERANCE = 0.001; if (sizes == null) return null; Size optimalSize = null; double minDiff = Double.MAX_VALUE; // Because of bugs of overlay and layout, we sometimes will try to // layout the viewfinder in the portrait orientation and thus get the // wrong size of mSurfaceView. When we change the preview size, the // new overlay will be created before the old one closed, which causes // an exception. For now, just get the screen size Display display = currentActivity.getWindowManager().getDefaultDisplay(); int targetHeight = Math.min(display.getHeight(), display.getWidth()); if (targetHeight <= 0) { // We don't know the size of SurfaceView, use screen height targetHeight = display.getHeight(); } // Try to find an size match aspect ratio and size for (Size size : sizes) { double ratio = (double) size.width / size.height; if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue; if (Math.abs(size.height - targetHeight) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - targetHeight); } } // Cannot find the one match the aspect ratio. This should not happen. // Ignore the requirement. if (optimalSize == null) { Log.w(TAG, "No preview size match the aspect ratio"); minDiff = Double.MAX_VALUE; for (Size size : sizes) { if (Math.abs(size.height - targetHeight) < minDiff) { optimalSize = size; minDiff = Math.abs(size.height - targetHeight); } } } return optimalSize; } public static void dumpParameters(Parameters parameters) { String flattened = parameters.flatten(); StringTokenizer tokenizer = new StringTokenizer(flattened, ";"); Log.d(TAG, "Dump all camera parameters:"); while (tokenizer.hasMoreElements()) { Log.d(TAG, tokenizer.nextToken()); } } /** * Returns whether the device is voice-capable (meaning, it can do MMS). */ public static boolean isMmsCapable(Context context) { TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); if (telephonyManager == null) { return false; } try { Class partypes[] = new Class[0]; Method sIsVoiceCapable = TelephonyManager.class.getMethod( "isVoiceCapable", partypes); Object arglist[] = new Object[0]; Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist); return (Boolean) retobj; } catch (java.lang.reflect.InvocationTargetException ite) { // Failure, must be another device. // Assume that it is voice capable. } catch (IllegalAccessException iae) { // Failure, must be an other device. // Assume that it is voice capable. } catch (NoSuchMethodException nsme) { } return true; } // This is for test only. Allow the camera to launch the specific camera. public static int getCameraFacingIntentExtras(Activity currentActivity) { int cameraId = -1; int intentCameraId = currentActivity.getIntent().getIntExtra(Util.EXTRAS_CAMERA_FACING, -1); if (isFrontCameraIntent(intentCameraId)) { // Check if the front camera exist int frontCameraId = CameraHolder.instance().getFrontCameraId(); if (frontCameraId != -1) { cameraId = frontCameraId; } } else if (isBackCameraIntent(intentCameraId)) { // Check if the back camera exist int backCameraId = CameraHolder.instance().getBackCameraId(); if (backCameraId != -1) { cameraId = backCameraId; } } return cameraId; } private static boolean isFrontCameraIntent(int intentCameraId) { return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT); } private static boolean isBackCameraIntent(int intentCameraId) { return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK); } private static int mLocation[] = new int[2]; // This method is not thread-safe. public static boolean pointInView(float x, float y, View v) { v.getLocationInWindow(mLocation); return x >= mLocation[0] && x < (mLocation[0] + v.getWidth()) && y >= mLocation[1] && y < (mLocation[1] + v.getHeight()); } }